feat: require explicit model-operation holds (AEG-V15-037)
Separates business holds from technical failures in the pure execution state machine. Evidence: targeted Release tests 3/3 passed; TRX SHA256 2F2CD06B1DFD3F76F336A0636598553599CD05CF7FA82E477DB160425975085F.
This commit is contained in:
@@ -15,7 +15,8 @@ public sealed record ModelOperationExecutionTransition(
|
||||
ModelOperationExecutionState To,
|
||||
string ReasonCode,
|
||||
string EvidenceHash,
|
||||
DateTimeOffset OccurredAt);
|
||||
DateTimeOffset OccurredAt,
|
||||
DateTimeOffset? HoldUntil);
|
||||
|
||||
public sealed class ModelOperationExecution
|
||||
{
|
||||
@@ -48,18 +49,30 @@ public sealed class ModelOperationExecution
|
||||
public DateTimeOffset RequestedAt { get; }
|
||||
public DateTimeOffset LastOccurredAt { get; private set; }
|
||||
public ModelOperationExecutionState State { get; private set; }
|
||||
public DateTimeOffset? HoldUntil { get; private set; }
|
||||
public IReadOnlyList<ModelOperationExecutionTransition> Transitions => transitions;
|
||||
|
||||
public ModelOperationExecutionTransition MoveTo(ModelOperationExecutionState next, string reasonCode, string evidenceHash, DateTimeOffset occurredAt)
|
||||
public ModelOperationExecutionTransition MoveTo(
|
||||
ModelOperationExecutionState next,
|
||||
string reasonCode,
|
||||
string evidenceHash,
|
||||
DateTimeOffset occurredAt,
|
||||
DateTimeOffset? holdUntil = null)
|
||||
{
|
||||
if (!Allowed[State].Contains(next)) throw new InvalidOperationException($"Execution transition {State} -> {next} is not allowed.");
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(reasonCode);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(evidenceHash);
|
||||
if (occurredAt < LastOccurredAt) throw new InvalidOperationException("Execution transition time cannot move backwards.");
|
||||
var transition = new ModelOperationExecutionTransition(State, next, reasonCode, evidenceHash, occurredAt);
|
||||
if (next == ModelOperationExecutionState.BusinessHold && (!holdUntil.HasValue || holdUntil <= occurredAt))
|
||||
throw new InvalidOperationException("Business hold requires a future hold-until instant.");
|
||||
if (next != ModelOperationExecutionState.BusinessHold && holdUntil.HasValue)
|
||||
throw new InvalidOperationException("Only a business hold may have a hold-until instant.");
|
||||
|
||||
var transition = new ModelOperationExecutionTransition(State, next, reasonCode, evidenceHash, occurredAt, holdUntil);
|
||||
transitions.Add(transition);
|
||||
State = next;
|
||||
LastOccurredAt = occurredAt;
|
||||
HoldUntil = holdUntil;
|
||||
return transition;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user