feat: bound scheduler catch-up dispatch (AEG-V15-034)

Implements LATEST_ONLY, SKIP_MISSED, and ALL_WITH_LIMIT dispatch plans anchored to scheduledFor. Evidence: targeted Release tests 4/4 passed; TRX SHA256 DC28BE4F2FCF511D5859B9FC3A0ADDF8CE3A566262C9848F05B06D825EA944AD. Schedules remain disabled; DEC-083 is not resolved.
This commit is contained in:
2026-08-09 02:01:25 +09:00
parent 6a86997438
commit dd352596fc
9 changed files with 172 additions and 18 deletions
@@ -33,21 +33,32 @@ public sealed class ModelOperationsDispatcherJob(
{
try
{
var job = Job.FromExpression<ScheduledModelOperationJob>(handler => handler.ExecuteAsync(
item.ScheduleId,
item.OperationCode,
item.ScopeKey,
item.AutomationMode,
item.IdempotencyKey));
var backgroundJobId = jobs.Create(job, new EnqueuedState(item.Queue));
var plan = ScheduleOccurrencePlanner.Plan(item.ScheduledFor, item.Cadence, item.CatchUpPolicy, item.MaxCatchUp, now);
if (plan.OccurrencesToDispatch.Count == 0)
{
await schedules.AdvanceWithoutDispatchAsync(item.ScheduleId, leaseOwner, now, plan.NextDueAt, CancellationToken.None);
continue;
}
var backgroundJobId = string.Empty;
foreach (var occurrence in plan.OccurrencesToDispatch)
{
var idempotencyKey = $"{item.OperationCode}:{item.ScopeKey}:{item.ScheduleVersion}:{occurrence.UtcDateTime:yyyyMMddHHmmss}Z";
var job = Job.FromExpression<ScheduledModelOperationJob>(handler => handler.ExecuteAsync(
item.ScheduleId,
item.OperationCode,
item.ScopeKey,
item.AutomationMode,
idempotencyKey));
backgroundJobId = jobs.Create(job, new EnqueuedState(item.Queue));
}
var nextDueAt = ScheduleOccurrencePlanner.GetNextDueAt(item.ScheduledFor, item.Cadence, item.CatchUpPolicy, item.MaxCatchUp, now);
await schedules.MarkDispatchedAsync(
item.ScheduleId,
leaseOwner,
backgroundJobId,
now,
nextDueAt,
plan.NextDueAt,
CancellationToken.None);
}
catch (Exception exception)