save checkpoint: save features

This commit is contained in:
master
2026-02-12 10:27:23 +02:00
parent dca86e1248
commit 5bca406787
8837 changed files with 1796879 additions and 5294 deletions

View File

@@ -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);
}