using Microsoft.Extensions.Configuration;
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central;
///
/// Tuning knobs for the central singleton.
/// Default cadence is 24 hours; the retention window itself
/// is sourced from
/// (default 365) so operators tune retention from a single section.
///
///
///
/// The purge actor is a daily-cadence singleton, not a hot-loop. The
/// partition switch itself is metadata-only (since the
/// AlignAuditLogEventIdUniqueness migration it no longer drops and rebuilds a
/// non-aligned unique index around it), but it still takes schema-modification
/// locks and competes with ingest for the same table. Lower this only when an
/// operator can prove they need sub-daily purge granularity.
///
///
/// exists for tests to drop the cadence to
/// milliseconds; production config is expected to set
/// only. Because this options class is Bind-ed wholesale, a config value
/// at AuditLog:Purge:IntervalOverride would bind if present (and would
/// bypass the minimum clamp) — operators must not set it.
///
///
public sealed class AuditLogPurgeOptions
{
/// Period of the purge tick in hours (default 24).
public int IntervalHours { get; set; } = 24;
/// Maximum rows deleted for each channel in one purge tick (default 50,000); remaining backlog resumes on later ticks.
public int MaxPurgedRowsPerChannelPerTick { get; set; } = 50_000;
///
/// Batch size for the per-channel retention-override row DELETE
/// ().
/// Each DELETE TOP (@batch) caps the transaction-log and lock footprint
/// per statement; the repository stops at
/// so a backlogged channel yields to later channels. Default 5000 keeps individual
/// deletes short on a busy central DB. Clamped to a sane minimum in
/// .
///
///
/// The operator-facing config key is ChannelPurgeBatchSize
/// (per Component-AuditLog.md), so the binder maps that documented key onto this
/// backing property via . The unattributed
/// property name (ChannelPurgeBatchSizeConfigured) would otherwise have been
/// the bind key, silently ignoring the documented section.
///
[ConfigurationKeyName("ChannelPurgeBatchSize")]
public int ChannelPurgeBatchSizeConfigured { get; set; } = 5000;
///
/// Resolves the effective per-channel purge batch size, clamped to at least 1 so
/// a misconfigured 0/negative value cannot make the repository's DELETE
/// loop spin or throw.
///
public int ChannelPurgeBatchSize => ChannelPurgeBatchSizeConfigured < 1 ? 1 : ChannelPurgeBatchSizeConfigured;
///
/// Per-command timeout (in minutes) for the maintenance SQL the purge tick issues —
/// both the partition switch-out staging batch
/// ()
/// and each per-channel DELETE TOP batch. Default 30 minutes.
///
///
/// The ADO.NET default command timeout is ~30 seconds. On a large or contended partition the
/// SWITCH batch can exceed that and abort mid-flight, leaving an orphaned staging table for the
/// next tick's CATCH branch to clean up (arch-review 04, S2). A generous maintenance timeout
/// lets the metadata-only switch complete rather than self-locking. Resolved via
/// , clamped to a 1-minute floor.
///
public int MaintenanceCommandTimeoutMinutes { get; set; } = 30;
///
/// Resolves the effective maintenance command timeout, clamped to at least 1 minute so a
/// misconfigured 0/negative value cannot yield a zero/negative timeout (which ADO.NET
/// interprets as "no timeout" for 0 and rejects for negatives).
///
public TimeSpan ResolvedMaintenanceCommandTimeout =>
TimeSpan.FromMinutes(MaintenanceCommandTimeoutMinutes < 1 ? 1 : MaintenanceCommandTimeoutMinutes);
///
/// Test-only override for finer control over the tick cadence than
/// whole-hour resolution allows. When non-null, takes precedence over
/// AND bypasses the
/// minimum clamp (so tests can use millisecond cadences). Production
/// config exposes only and never sets this
/// knob — but because the options class is Bind-ed wholesale, a
/// config value at AuditLog:Purge:IntervalOverride WOULD bind if
/// present; operators must not set it.
///
public TimeSpan? IntervalOverride { get; set; }
///
/// Minimum interval the config-bound can
/// resolve to. Clamps a misconfigured IntervalHours: 0 (or a
/// negative value) away from — a zero
/// interval would make Akka's ScheduleTellRepeatedlyCancelable
/// spin, looping the partition drop/rebuild dance into a sustained SQL
/// outage. The test-only bypasses this
/// clamp so unit tests can still drop the cadence to milliseconds.
///
private static readonly TimeSpan MinConfiguredInterval = TimeSpan.FromMinutes(1);
///
/// Resolves the effective tick interval, honouring the test override
/// when set. Falls back to , clamped to at
/// least so a zero/negative config
/// value can never yield (which would spin
/// the scheduler).
///
public TimeSpan Interval
{
get
{
if (IntervalOverride is { } overrideValue)
{
return overrideValue;
}
var resolved = TimeSpan.FromHours(IntervalHours);
return resolved < MinConfiguredInterval ? MinConfiguredInterval : resolved;
}
}
}