feat(auditlog): AuditLogPartitionMaintenanceService monthly roll-forward (#23 M6)

This commit is contained in:
Joseph Doherty
2026-05-20 18:51:43 -04:00
parent cc2d6e91f1
commit 75b060e0a8
9 changed files with 834 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
namespace ScadaLink.Commons.Interfaces;
/// <summary>
/// Abstraction over the central AuditLog partition-function roll-forward
/// operation. M6-T5 introduces a daily-cadence hosted service
/// (<c>AuditLogPartitionMaintenanceService</c>) that calls
/// <see cref="EnsureLookaheadAsync"/> to make sure
/// <c>pf_AuditLog_Month</c> always has at least <c>LookaheadMonths</c> of
/// future boundaries available — otherwise inserts past the highest
/// boundary land in a single ever-growing tail partition that
/// <c>SwitchOutPartitionAsync</c> cannot purge cleanly.
/// </summary>
/// <remarks>
/// <para>
/// The interface lives in <c>ScadaLink.Commons</c> so the central hosted
/// service in <c>ScadaLink.AuditLog</c> can depend on it without taking a
/// reference on <c>ScadaLink.ConfigurationDatabase</c>; the EF-based
/// implementation ships in
/// <c>ScadaLink.ConfigurationDatabase.Maintenance.AuditLogPartitionMaintenance</c>
/// and is registered by <c>AddConfigurationDatabase</c>.
/// </para>
/// <para>
/// Both methods read <c>sys.partition_range_values</c> / mutate
/// <c>pf_AuditLog_Month</c> via raw SQL — there is no EF model for a
/// partition function. The interface deliberately exposes only the two
/// operations the hosted service needs; it is not a general partition-DDL
/// surface.
/// </para>
/// </remarks>
public interface IPartitionMaintenance
{
/// <summary>
/// Splits new monthly boundaries on <c>pf_AuditLog_Month</c> so the
/// function covers at least <paramref name="lookaheadMonths"/> future
/// months relative to <see cref="DateTime.UtcNow"/>. Idempotent — a
/// boundary that already exists is skipped rather than re-issued.
/// Returns the boundaries actually added, in chronological order.
/// </summary>
Task<IReadOnlyList<DateTime>> EnsureLookaheadAsync(int lookaheadMonths, CancellationToken ct = default);
/// <summary>
/// Reads the current maximum boundary value from
/// <c>sys.partition_range_values</c> for <c>pf_AuditLog_Month</c>.
/// Returns <c>null</c> when the partition function does not exist or
/// has no boundaries.
/// </summary>
Task<DateTime?> GetMaxBoundaryAsync(CancellationToken ct = default);
}