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:
@@ -40,4 +40,26 @@ public sealed class ModelOperationExecutionTests
|
||||
execution.MoveTo(ModelOperationExecutionState.Running, "CALENDAR_APPROVED", "h2", holdUntil);
|
||||
Assert.Null(execution.HoldUntil);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Running_execution_records_monotonic_heartbeats_and_uses_the_caller_cutoff()
|
||||
{
|
||||
var now = new DateTimeOffset(2026, 8, 9, 0, 0, 0, TimeSpan.Zero);
|
||||
var execution = new ModelOperationExecution(Guid.NewGuid(), "key", now);
|
||||
execution.MoveTo(ModelOperationExecutionState.Running, "STARTED", "h1", now);
|
||||
execution.RecordHeartbeat(now.AddMinutes(2));
|
||||
|
||||
Assert.False(execution.HasExceededHeartbeatCutoff(now.AddMinutes(6), TimeSpan.FromMinutes(5)));
|
||||
Assert.True(execution.HasExceededHeartbeatCutoff(now.AddMinutes(8), TimeSpan.FromMinutes(5)));
|
||||
Assert.Throws<InvalidOperationException>(() => execution.RecordHeartbeat(now.AddMinutes(1)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Non_running_execution_cannot_record_a_heartbeat()
|
||||
{
|
||||
var now = new DateTimeOffset(2026, 8, 9, 0, 0, 0, TimeSpan.Zero);
|
||||
var execution = new ModelOperationExecution(Guid.NewGuid(), "key", now);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => execution.RecordHeartbeat(now));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user