69 lines
3.1 KiB
C#
69 lines
3.1 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Baya.Application.Contracts.Audit;
|
|
using Baya.Application.Contracts.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Baya.Infrastructure.Persistence.Services.Scheduling.Jobs;
|
|
|
|
/// <summary>
|
|
/// Retention/archival policy for the append-only <c>ops.AuditLogs</c> table (refinement-phase-9 §9.4). The audit
|
|
/// trail is never pruned by the application except here, on a schedule, and even then a two-tier policy protects
|
|
/// the legally-sensitive rows: <b>financial & verification</b> entity types (refunds, clawbacks, payouts,
|
|
/// payout batches, verification decisions, config changes, partner centers) keep a long window
|
|
/// (<c>audit_retention_financial_days</c>, default ~7 years); everyday operational rows keep a shorter one
|
|
/// (<c>audit_retention_general_days</c>, default ~2 years). Idempotent: a re-run simply finds nothing new to
|
|
/// delete. Runs on the <c>audit_retention_scan_cadence_hours</c> cadence.
|
|
/// </summary>
|
|
internal sealed class AuditLogRetentionJob(ILogger<AuditLogRetentionJob> logger) : IRecurringJob
|
|
{
|
|
// Bound the per-run working set so a large backlog drains across successive runs without loading the whole table.
|
|
private const int MaxRowsPerRun = 20_000;
|
|
|
|
/// <summary>
|
|
/// The <c>IAuditable</c> entity types whose audit rows carry money- or verification-legal weight. Their
|
|
/// <c>EntityType</c> is the CLR type name written by <c>AuditFieldInterceptor</c>. Everything else uses the
|
|
/// shorter general window.
|
|
/// </summary>
|
|
private static readonly IReadOnlyCollection<string> FinancialEntityTypes =
|
|
[
|
|
"Refund",
|
|
"NurseClawback",
|
|
"NursePayout",
|
|
"NursePayoutBatch",
|
|
"NurseVerification",
|
|
"PlatformConfig",
|
|
"PartnerCenter"
|
|
];
|
|
|
|
public string Name => "audit_log_retention";
|
|
|
|
public async ValueTask<TimeSpan> GetIntervalAsync(IServiceProvider services, CancellationToken cancellationToken)
|
|
{
|
|
var config = services.GetRequiredService<IPlatformConfig>();
|
|
var hours = await config.GetConfig<int>("audit_retention_scan_cadence_hours", cancellationToken);
|
|
return TimeSpan.FromHours(hours);
|
|
}
|
|
|
|
public async ValueTask RunAsync(IServiceProvider services, CancellationToken cancellationToken)
|
|
{
|
|
var config = services.GetRequiredService<IPlatformConfig>();
|
|
var audit = services.GetRequiredService<IAuditLogger>();
|
|
|
|
var generalDays = await config.GetConfig<int>("audit_retention_general_days", cancellationToken);
|
|
var financialDays = await config.GetConfig<int>("audit_retention_financial_days", cancellationToken);
|
|
|
|
var deleted = await audit.PurgeExpiredAsync(
|
|
generalDays, financialDays, FinancialEntityTypes, MaxRowsPerRun, cancellationToken);
|
|
|
|
if (deleted > 0)
|
|
logger.LogInformation(
|
|
"Audit retention purged {Count} audit row(s) (general>{GeneralDays}d, financial>{FinancialDays}d)",
|
|
deleted, generalDays, financialDays);
|
|
}
|
|
}
|