diff --git a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/SiteAuditRetentionOptions.cs b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/SiteAuditRetentionOptions.cs
new file mode 100644
index 00000000..3aee06a9
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/SiteAuditRetentionOptions.cs
@@ -0,0 +1,60 @@
+namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
+
+///
+/// Configuration for the site-side SQLite retention purge (PLAN-04 Task 2, S1/U2).
+/// The site audit store is ephemeral (~7-day retention);
+/// runs
+/// on a timer using these knobs. Mirrors the resolve/clamp shape of
+/// SiteCallAuditOptions so a misconfigured value can never make the timer spin
+/// or the retention window collapse.
+///
+public class SiteAuditRetentionOptions
+{
+ /// Configuration section path bound in the site host.
+ public const string SectionName = "ScadaBridge:AuditLog:SiteRetention";
+
+ ///
+ /// Retention window in days. On each purge tick, Forwarded/Reconciled rows whose
+ /// OccurredAtUtc is older than UtcNow - ResolvedRetentionDays are
+ /// deleted (Pending rows are never purged on age alone). Default 7. Resolved value
+ /// is clamped to ...
+ ///
+ public int RetentionDays { get; set; } = 7;
+
+ private const int MinRetentionDays = 1;
+ private const int MaxRetentionDays = 90;
+
+ ///
+ /// Effective retention window: clamped to
+ /// [, ] so a
+ /// misconfigured 0 (which would purge everything) or an unbounded value can
+ /// never take effect.
+ ///
+ public int ResolvedRetentionDays =>
+ RetentionDays < MinRetentionDays ? MinRetentionDays
+ : RetentionDays > MaxRetentionDays ? MaxRetentionDays
+ : RetentionDays;
+
+ ///
+ /// Period of the purge tick. Default 24 hours. Resolved value is clamped to at
+ /// least so a zero/negative config value cannot
+ /// make the timer fire in a tight loop.
+ ///
+ public TimeSpan PurgeInterval { get; set; } = TimeSpan.FromHours(24);
+
+ private static readonly TimeSpan MinPurgeInterval = TimeSpan.FromMinutes(1);
+
+ ///
+ /// Effective purge period: clamped to at least
+ /// (the Timer/Akka zero-interval spin footgun).
+ ///
+ public TimeSpan ResolvedPurgeInterval =>
+ PurgeInterval < MinPurgeInterval ? MinPurgeInterval : PurgeInterval;
+
+ ///
+ /// Delay before the first purge tick. Default 5 minutes — short so a node that
+ /// restarts daily (or more often) still runs the purge shortly after start
+ /// rather than waiting a full it may never reach.
+ ///
+ public TimeSpan InitialDelay { get; set; } = TimeSpan.FromMinutes(5);
+}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests/Site/SiteAuditRetentionOptionsTests.cs b/tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests/Site/SiteAuditRetentionOptionsTests.cs
new file mode 100644
index 00000000..da9d66da
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests/Site/SiteAuditRetentionOptionsTests.cs
@@ -0,0 +1,33 @@
+using ZB.MOM.WW.ScadaBridge.AuditLog.Site;
+
+namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Site;
+
+///
+/// Tests for clamping (PLAN-04 Task 2).
+/// The resolved knobs guard the daily site retention job against misconfiguration:
+/// retention days are clamped to [1, 90]; the purge interval is clamped away from
+/// TimeSpan.Zero (an Akka/Timer spin footgun).
+///
+public class SiteAuditRetentionOptionsTests
+{
+ [Theory]
+ [InlineData(0, 1)] // clamp floor
+ [InlineData(7, 7)] // default passthrough
+ [InlineData(365, 90)] // clamp ceiling (spec: min 1, max 90)
+ public void ResolvedRetentionDays_Clamps(int configured, int expected)
+ => Assert.Equal(expected, new SiteAuditRetentionOptions { RetentionDays = configured }.ResolvedRetentionDays);
+
+ [Fact]
+ public void Defaults_Are_SevenDays_DailyPurge_FiveMinuteInitialDelay()
+ {
+ var o = new SiteAuditRetentionOptions();
+ Assert.Equal(7, o.ResolvedRetentionDays);
+ Assert.Equal(TimeSpan.FromHours(24), o.ResolvedPurgeInterval);
+ Assert.Equal(TimeSpan.FromMinutes(5), o.InitialDelay);
+ }
+
+ [Fact]
+ public void ResolvedPurgeInterval_ClampsZeroToOneMinute()
+ => Assert.Equal(TimeSpan.FromMinutes(1),
+ new SiteAuditRetentionOptions { PurgeInterval = TimeSpan.Zero }.ResolvedPurgeInterval);
+}