feat: preserve due operation provenance (AEG-V15-035)

Carries scheduledFor, catch-up policy, and maxCatchUp from the scheduler through the request model and transactional outbox. Evidence: targeted Release tests 5/5 passed; TRX SHA256 C1BF3EF274702305A29673D5B6A1C3A98D08B1716DA3CD8CB0EE710B5E6C12E6. Schedules remain disabled.
This commit is contained in:
2026-08-09 02:04:26 +09:00
parent dd352596fc
commit d18f6a7a67
10 changed files with 168 additions and 5 deletions
@@ -14,6 +14,9 @@ public sealed class ModelOperationRequestService(
string scopeKey,
string automationMode,
string idempotencyKey,
DateTimeOffset scheduledFor,
string catchUpPolicy,
int maxCatchUp,
string correlationId,
CancellationToken cancellationToken)
{
@@ -21,6 +24,8 @@ public sealed class ModelOperationRequestService(
ModelOperationExecutionBoundary.EnsureAllowed(definition);
if (!definition.AutomationMode.ToContractValue().Equals(automationMode, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("Schedule automation mode does not match the approved operation registry.");
if (maxCatchUp is < 0 or > 31) throw new ArgumentOutOfRangeException(nameof(maxCatchUp));
if (string.IsNullOrWhiteSpace(catchUpPolicy)) throw new ArgumentException("Catch-up policy is required.", nameof(catchUpPolicy));
var now = clock.UtcNow;
var context = await contextReader.ReadAsync(scopeKey, now, cancellationToken);
@@ -35,6 +40,9 @@ public sealed class ModelOperationRequestService(
scopeKey,
definition.AutomationMode.ToContractValue(),
idempotencyKey,
scheduledFor,
catchUpPolicy,
maxCatchUp,
context,
correlationId,
now);
@@ -28,6 +28,9 @@ public sealed record ModelOperationRequest(
string ScopeKey,
string AutomationMode,
string IdempotencyKey,
DateTimeOffset ScheduledFor,
string CatchUpPolicy,
int MaxCatchUp,
ApprovedModelContext Context,
string CorrelationId,
DateTimeOffset RequestedAt);
@@ -82,6 +85,9 @@ public interface IModelOperationRequestService
string scopeKey,
string automationMode,
string idempotencyKey,
DateTimeOffset scheduledFor,
string catchUpPolicy,
int maxCatchUp,
string correlationId,
CancellationToken cancellationToken);
}
@@ -15,11 +15,11 @@ public sealed class DapperModelOperationRequestRepository(
insert into evaluation.model_operation_request
(request_id, schedule_id, operation_code, scope_key, automation_mode, idempotency_key,
dataset_id, data_hash, model_version, config_version, code_sha, contract_version,
lifecycle_state, correlation_id, status, requested_at)
lifecycle_state, correlation_id, status, requested_at, scheduled_for)
values
(@RequestId, @ScheduleId, @OperationCode, @ScopeKey, @AutomationMode, @IdempotencyKey,
@DatasetId, @DataHash, @ModelVersion, @ConfigVersion, @CodeSha, @ContractVersion,
@LifecycleState, @CorrelationId, 'REQUESTED', @RequestedAt)
@LifecycleState, @CorrelationId, 'REQUESTED', @RequestedAt, @ScheduledFor)
on conflict (idempotency_key) do nothing;
""";
@@ -47,6 +47,7 @@ public sealed class DapperModelOperationRequestRepository(
request.ScopeKey,
request.AutomationMode,
request.IdempotencyKey,
request.ScheduledFor,
request.Context.VersionSet.DatasetId,
request.Context.VersionSet.DataHash,
request.Context.VersionSet.ModelVersion,
@@ -68,6 +69,9 @@ public sealed class DapperModelOperationRequestRepository(
request.OperationCode,
request.ScopeKey,
request.AutomationMode,
request.ScheduledFor,
request.CatchUpPolicy,
request.MaxCatchUp,
versionSet = request.Context.VersionSet,
boundary = "NO_AUTO_MODEL_MUTATION"
});
@@ -49,7 +49,10 @@ public sealed class ModelOperationsDispatcherJob(
item.OperationCode,
item.ScopeKey,
item.AutomationMode,
idempotencyKey));
idempotencyKey,
occurrence,
item.CatchUpPolicy,
item.MaxCatchUp));
backgroundJobId = jobs.Create(job, new EnqueuedState(item.Queue));
}
@@ -26,7 +26,10 @@ public sealed class ScheduledModelOperationJob(
string operationCode,
string scopeKey,
string automationMode,
string idempotencyKey)
string idempotencyKey,
DateTimeOffset scheduledFor,
string catchUpPolicy,
int maxCatchUp)
{
var correlationId = $"model-operation:{operationCode}:{Guid.NewGuid():N}";
var request = await service.RequestAsync(
@@ -35,6 +38,9 @@ public sealed class ScheduledModelOperationJob(
scopeKey,
automationMode,
idempotencyKey,
scheduledFor,
catchUpPolicy,
maxCatchUp,
correlationId,
CancellationToken.None);