feat: add deterministic execution heartbeats (AEG-V15-038)
Adds a pure, monotonic execution heartbeat and caller-supplied staleness cutoff without inventing alert thresholds. Evidence: targeted Release tests 5/5 passed; TRX SHA256 2ADBB526FAF6E5D924EB3F53C7E582E736199A59E0DA4E4FD25DCAA82A661BBC. WBS remains IN_PROGRESS pending approved alert contract.
This commit is contained in:
@@ -48,6 +48,7 @@ public sealed class ModelOperationExecution
|
||||
public string IdempotencyKey { get; }
|
||||
public DateTimeOffset RequestedAt { get; }
|
||||
public DateTimeOffset LastOccurredAt { get; private set; }
|
||||
public DateTimeOffset? LastHeartbeatAt { get; private set; }
|
||||
public ModelOperationExecutionState State { get; private set; }
|
||||
public DateTimeOffset? HoldUntil { get; private set; }
|
||||
public IReadOnlyList<ModelOperationExecutionTransition> Transitions => transitions;
|
||||
@@ -73,8 +74,28 @@ public sealed class ModelOperationExecution
|
||||
State = next;
|
||||
LastOccurredAt = occurredAt;
|
||||
HoldUntil = holdUntil;
|
||||
if (next == ModelOperationExecutionState.Running) LastHeartbeatAt = occurredAt;
|
||||
return transition;
|
||||
}
|
||||
|
||||
public void RecordHeartbeat(DateTimeOffset occurredAt)
|
||||
{
|
||||
if (State != ModelOperationExecutionState.Running)
|
||||
throw new InvalidOperationException("Only a running execution may record a heartbeat.");
|
||||
if (occurredAt < LastHeartbeatAt)
|
||||
throw new InvalidOperationException("Execution heartbeat time cannot move backwards.");
|
||||
|
||||
LastHeartbeatAt = occurredAt;
|
||||
}
|
||||
|
||||
public bool HasExceededHeartbeatCutoff(DateTimeOffset asOf, TimeSpan allowedSilence)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(allowedSilence, TimeSpan.Zero);
|
||||
if (asOf < LastHeartbeatAt) throw new InvalidOperationException("Heartbeat cutoff cannot precede the latest heartbeat.");
|
||||
return State == ModelOperationExecutionState.Running
|
||||
&& LastHeartbeatAt.HasValue
|
||||
&& asOf - LastHeartbeatAt.Value > allowedSilence;
|
||||
}
|
||||
|
||||
private static HashSet<ModelOperationExecutionState> Set(params ModelOperationExecutionState[] states) => new(states);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user