feat(audit-log): daily site SQLite retention purge job (closes unbounded site DB growth)

SiteAuditRetentionService (site-only IHostedService) ticks PurgeExpiredAsync on
InitialDelay(5m)/PurgeInterval(24h, clamped >=1m), cutoff = UtcNow - RetentionDays.
Registered site-only in AddAuditLogHealthMetricsBridge; options bound from
AuditLog:SiteRetention. Per-tick failures swallowed. Added a test-only
PurgeIntervalOverride (mirrors SiteCallAuditOptions) so the ms-cadence tick test
can bypass the 1-min production clamp. Doc updated. (PLAN-04 Task 3, S1/U2)
This commit is contained in:
Joseph Doherty
2026-07-09 06:22:19 -04:00
parent b5271da5c8
commit 74aa872c07
5 changed files with 292 additions and 7 deletions
@@ -101,6 +101,11 @@ public static class ServiceCollectionExtensions
.Bind(config.GetSection(SiteWriterSectionName));
services.AddOptions<SiteAuditTelemetryOptions>()
.Bind(config.GetSection(SiteTelemetrySectionName));
// Retention purge options (the SiteAuditRetentionService hosted job is
// registered site-only in AddAuditLogHealthMetricsBridge). Binding here is
// inert on central — no hosted service consumes it there.
services.AddOptions<SiteAuditRetentionOptions>()
.Bind(config.GetSection(SiteAuditRetentionOptions.SectionName));
// SqliteAuditWriter is a singleton with a single owned SqliteConnection
// and a background writer Task; multiple instances would race on the
@@ -316,6 +321,12 @@ public static class ServiceCollectionExtensions
// free of hosted-service registrations that would resolve a missing
// ISiteHealthCollector on central.
services.AddHostedService<SiteAuditBacklogReporter>();
// Site-only retention purge: runs ISiteAuditQueue.PurgeExpiredAsync on a
// daily timer so the ephemeral site SQLite store does not grow unbounded.
// Registered here (not in AddAuditLog, which also runs on central) so the
// hosted service only starts on site composition roots. Guarded by the same
// SiteAuditBacklogReporter sentinel above against double-registration.
services.AddHostedService<SiteAuditRetentionService>();
return services;
}