save checkpoint: save features
This commit is contained in:
@@ -272,14 +272,118 @@ internal sealed class ActionExecutor : IActionExecutor
|
||||
executionId, proposal, context, ActionAuditOutcome.ApprovalRequested,
|
||||
decision, null, cancellationToken, approvalRequest.RequestId);
|
||||
|
||||
if (!_options.AwaitApprovalCompletion)
|
||||
{
|
||||
return new ActionExecutionResult
|
||||
{
|
||||
ExecutionId = executionId,
|
||||
Outcome = ActionExecutionOutcome.PendingApproval,
|
||||
Message = $"Approval required from: {string.Join(", ", decision.RequiredApprovers.Select(a => a.Identifier))}",
|
||||
StartedAt = startedAt,
|
||||
CompletedAt = null, // Not completed yet
|
||||
CanRollback = false,
|
||||
OutputData = new Dictionary<string, string>
|
||||
{
|
||||
["approvalRequestId"] = approvalRequest.RequestId,
|
||||
["approvalWorkflowId"] = approvalRequest.WorkflowId
|
||||
}.ToImmutableDictionary()
|
||||
};
|
||||
}
|
||||
|
||||
var approvalResult = await _approvalAdapter.WaitForApprovalAsync(
|
||||
approvalRequest.RequestId,
|
||||
_options.ApprovalWaitTimeout,
|
||||
cancellationToken);
|
||||
|
||||
if (approvalResult.Approved)
|
||||
{
|
||||
await RecordAuditEntryAsync(
|
||||
executionId,
|
||||
proposal,
|
||||
context,
|
||||
ActionAuditOutcome.Approved,
|
||||
decision,
|
||||
null,
|
||||
cancellationToken,
|
||||
approvalRequest.RequestId,
|
||||
approvalResult.ApproverId);
|
||||
|
||||
var executedResult = await ExecuteImmediatelyAsync(
|
||||
executionId,
|
||||
proposal,
|
||||
context,
|
||||
decision,
|
||||
startedAt,
|
||||
cancellationToken);
|
||||
|
||||
return executedResult with
|
||||
{
|
||||
OutputData = executedResult.OutputData
|
||||
.SetItem("approvalRequestId", approvalRequest.RequestId)
|
||||
.SetItem("approvalWorkflowId", approvalRequest.WorkflowId)
|
||||
.SetItem("approvedBy", approvalResult.ApproverId ?? string.Empty)
|
||||
};
|
||||
}
|
||||
|
||||
if (approvalResult.TimedOut)
|
||||
{
|
||||
await RecordAuditEntryAsync(
|
||||
executionId,
|
||||
proposal,
|
||||
context,
|
||||
ActionAuditOutcome.ApprovalTimedOut,
|
||||
decision,
|
||||
approvalResult.DenialReason,
|
||||
cancellationToken,
|
||||
approvalRequest.RequestId);
|
||||
|
||||
return new ActionExecutionResult
|
||||
{
|
||||
ExecutionId = executionId,
|
||||
Outcome = ActionExecutionOutcome.Timeout,
|
||||
Message = $"Approval timed out: {approvalResult.DenialReason}",
|
||||
StartedAt = startedAt,
|
||||
CompletedAt = _timeProvider.GetUtcNow(),
|
||||
CanRollback = false,
|
||||
Error = new ActionError
|
||||
{
|
||||
Code = "APPROVAL_TIMED_OUT",
|
||||
Message = approvalResult.DenialReason ?? "Timed out waiting for approval",
|
||||
IsRetryable = true
|
||||
},
|
||||
OutputData = new Dictionary<string, string>
|
||||
{
|
||||
["approvalRequestId"] = approvalRequest.RequestId,
|
||||
["approvalWorkflowId"] = approvalRequest.WorkflowId
|
||||
}.ToImmutableDictionary()
|
||||
};
|
||||
}
|
||||
|
||||
await RecordAuditEntryAsync(
|
||||
executionId,
|
||||
proposal,
|
||||
context,
|
||||
ActionAuditOutcome.ApprovalDenied,
|
||||
decision,
|
||||
approvalResult.DenialReason,
|
||||
cancellationToken,
|
||||
approvalRequest.RequestId,
|
||||
approvalResult.ApproverId);
|
||||
|
||||
return new ActionExecutionResult
|
||||
{
|
||||
ExecutionId = executionId,
|
||||
Outcome = ActionExecutionOutcome.PendingApproval,
|
||||
Message = $"Approval required from: {string.Join(", ", decision.RequiredApprovers.Select(a => a.Identifier))}",
|
||||
Outcome = ActionExecutionOutcome.Failed,
|
||||
Message = $"Approval denied: {approvalResult.DenialReason}",
|
||||
StartedAt = startedAt,
|
||||
CompletedAt = null, // Not completed yet
|
||||
CompletedAt = _timeProvider.GetUtcNow(),
|
||||
CanRollback = false,
|
||||
Error = new ActionError
|
||||
{
|
||||
Code = "APPROVAL_DENIED",
|
||||
Message = approvalResult.DenialReason ?? "Action approval denied",
|
||||
IsRetryable = false
|
||||
},
|
||||
OutputData = new Dictionary<string, string>
|
||||
{
|
||||
["approvalRequestId"] = approvalRequest.RequestId,
|
||||
@@ -382,7 +486,8 @@ internal sealed class ActionExecutor : IActionExecutor
|
||||
ActionPolicyDecision? decision,
|
||||
string? errorMessage,
|
||||
CancellationToken cancellationToken,
|
||||
string? approvalRequestId = null)
|
||||
string? approvalRequestId = null,
|
||||
string? approverId = null)
|
||||
{
|
||||
var entry = new ActionAuditEntry
|
||||
{
|
||||
@@ -399,6 +504,7 @@ internal sealed class ActionExecutor : IActionExecutor
|
||||
PolicyId = decision?.PolicyId,
|
||||
PolicyResult = decision?.Decision,
|
||||
ApprovalRequestId = approvalRequestId,
|
||||
ApproverId = approverId,
|
||||
Parameters = proposal.Parameters,
|
||||
ExecutionId = executionId,
|
||||
ErrorMessage = errorMessage
|
||||
@@ -454,4 +560,14 @@ public sealed class ActionExecutorOptions
|
||||
/// Default timeout for action execution.
|
||||
/// </summary>
|
||||
public TimeSpan DefaultTimeout { get; set; } = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>
|
||||
/// Whether ExecuteAsync should wait for approval and continue execution when approved.
|
||||
/// </summary>
|
||||
public bool AwaitApprovalCompletion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum wait time when awaiting approval completion.
|
||||
/// </summary>
|
||||
public TimeSpan ApprovalWaitTimeout { get; set; } = TimeSpan.FromMinutes(5);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user