Skip to content

Commit aa6b3dd

Browse files
chrisli30weiliciousCopilot
authored
refactor: replace PARTIAL_SUCCESS with clear SUCCESS/FAILED/ERROR execution status (#521)
Co-authored-by: Wei Lin <wei@avaprotocol.org> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 2c60d1a commit aa6b3dd

11 files changed

Lines changed: 220 additions & 194 deletions

File tree

core/taskengine/engine.go

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3051,7 +3051,7 @@ func (n *Engine) SimulateTask(user *model.User, trigger *avsproto.TaskTrigger, n
30513051
}
30523052

30533053
// Step 10: Analyze execution results from all steps
3054-
_, executionError, failedStepCount, resultStatus := vm.AnalyzeExecutionResult()
3054+
executionError, failedStepCount, resultStatus := vm.AnalyzeExecutionResult()
30553055

30563056
// Step 11: Calculate total gas cost for the workflow
30573057

@@ -3073,20 +3073,7 @@ func (n *Engine) SimulateTask(user *model.User, trigger *avsproto.TaskTrigger, n
30733073
switch resultStatus {
30743074
case ExecutionSuccess:
30753075
n.logger.Info("workflow simulation completed successfully", "task_id", task.Id, "simulation_id", simulationID, "steps", len(execution.Steps))
3076-
case ExecutionPartialSuccess:
3077-
// Clean up error message to avoid stack traces in logs
3078-
cleanErrorMsg := executionError
3079-
stackTraceRegex := regexp.MustCompile(`(?m)^\s*at .*$`)
3080-
cleanErrorMsg = stackTraceRegex.ReplaceAllString(cleanErrorMsg, "")
3081-
cleanErrorMsg = strings.TrimSpace(cleanErrorMsg)
3082-
3083-
n.logger.Warn("workflow simulation completed with partial success",
3084-
"error", cleanErrorMsg,
3085-
"task_id", task.Id,
3086-
"simulation_id", simulationID,
3087-
"failed_steps", failedStepCount,
3088-
"total_steps", len(vm.ExecutionLogs))
3089-
case ExecutionFailure:
3076+
case ExecutionFailed:
30903077
// Clean up error message to avoid stack traces in logs
30913078
cleanErrorMsg := executionError
30923079
stackTraceRegex := regexp.MustCompile(`(?m)^\s*at .*$`)
@@ -3103,12 +3090,10 @@ func (n *Engine) SimulateTask(user *model.User, trigger *avsproto.TaskTrigger, n
31033090

31043091
// Handle VM-level errors if they occurred
31053092
if runErr != nil {
3106-
// This should not happen if AnalyzeExecutionResult is working correctly,
3107-
// but handle it as a fallback for VM-level errors
31083093
n.logger.Error("workflow simulation had VM-level error", "vm_error", runErr, "task_id", task.Id, "simulation_id", simulationID)
31093094
if execution.Error == "" {
31103095
execution.Error = fmt.Sprintf("VM execution error: %s", runErr.Error())
3111-
execution.Status = avsproto.ExecutionStatus_EXECUTION_STATUS_FAILED
3096+
execution.Status = avsproto.ExecutionStatus_EXECUTION_STATUS_ERROR
31123097
}
31133098
}
31143099

core/taskengine/executor.go

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ func (x *TaskExecutor) RunTask(task *model.Task, queueData *QueueExecutionData)
618618
}
619619

620620
// Analyze execution results from all steps (including failed ones)
621-
_, executionError, failedStepCount, resultStatus := vm.AnalyzeExecutionResult()
621+
executionError, failedStepCount, resultStatus := vm.AnalyzeExecutionResult()
622622

623623
// Calculate total gas cost for the workflow
624624

@@ -652,14 +652,7 @@ func (x *TaskExecutor) RunTask(task *model.Task, queueData *QueueExecutionData)
652652
switch resultStatus {
653653
case ExecutionSuccess:
654654
x.logger.Info("task execution completed successfully", "task_id", task.Id, "execution_id", queueData.ExecutionID, "total_steps", len(vm.ExecutionLogs))
655-
case ExecutionPartialSuccess:
656-
x.logger.Warn("task execution completed with partial success",
657-
"error", executionError,
658-
"task_id", task.Id,
659-
"execution_id", queueData.ExecutionID,
660-
"failed_steps", failedStepCount,
661-
"total_steps", len(vm.ExecutionLogs))
662-
case ExecutionFailure:
655+
case ExecutionFailed:
663656
x.logger.Error("task execution completed with failures",
664657
"error", executionError,
665658
"task_id", task.Id,
@@ -669,12 +662,10 @@ func (x *TaskExecutor) RunTask(task *model.Task, queueData *QueueExecutionData)
669662
}
670663

671664
if runTaskErr != nil {
672-
// This should not happen if AnalyzeExecutionResult is working correctly,
673-
// but handle it as a fallback for VM-level errors
674665
x.logger.Error("task execution had VM-level error", "vm_error", runTaskErr, "task_id", task.Id, "execution_id", queueData.ExecutionID)
675666
if execution.Error == "" {
676667
execution.Error = fmt.Sprintf("VM execution error: %s", runTaskErr.Error())
677-
execution.Status = avsproto.ExecutionStatus_EXECUTION_STATUS_FAILED
668+
execution.Status = avsproto.ExecutionStatus_EXECUTION_STATUS_ERROR
678669
}
679670
}
680671

@@ -732,10 +723,8 @@ func (x *TaskExecutor) RunTask(task *model.Task, queueData *QueueExecutionData)
732723
switch resultStatus {
733724
case ExecutionSuccess:
734725
x.logger.Info("successfully executing task", "task_id", task.Id, "triggermark", queueData)
735-
case ExecutionPartialSuccess:
736-
x.logger.Info("task execution completed with partial success", "task_id", task.Id, "failed_steps", failedStepCount, "triggermark", queueData)
737-
default: // ExecutionFailure or other
738-
x.logger.Warn("task execution completed with step failures", "task_id", task.Id, "failed_steps", failedStepCount)
726+
case ExecutionFailed:
727+
x.logger.Warn("task execution completed with step failures", "task_id", task.Id, "failed_steps", failedStepCount, "triggermark", queueData)
739728
}
740729

741730
return execution, nil

core/taskengine/executor_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package taskengine
33
import (
44
"net/http"
55
"net/http/httptest"
6-
"strings"
76
"testing"
87
"time"
98

@@ -225,14 +224,15 @@ func TestExecutorRunTaskWithBranchSilentFailureBehavior(t *testing.T) {
225224
t.Errorf("Expected no error with silent failure behavior, but got: %v", err)
226225
}
227226

228-
// Branch workflows with skipped nodes should report PARTIAL_SUCCESS
229-
if execution.Status != avsproto.ExecutionStatus_EXECUTION_STATUS_PARTIAL_SUCCESS {
230-
t.Errorf("Expected partial success status (branch path with skipped nodes), but got: %v with error: %s", execution.Status, execution.Error)
227+
// Branch workflows with skipped nodes are SUCCESS — the workflow executed its
228+
// chosen path correctly; skipping nodes due to branching is expected behavior.
229+
if execution.Status != avsproto.ExecutionStatus_EXECUTION_STATUS_SUCCESS {
230+
t.Errorf("Expected success status (branch path with skipped nodes is normal), but got: %v with error: %s", execution.Status, execution.Error)
231231
}
232232

233-
// Should have a partial execution message explaining the branching
234-
if execution.Error == "" || !strings.Contains(execution.Error, "Partial execution") {
235-
t.Errorf("Expected partial execution message, but got: %s", execution.Error)
233+
// No error when all executed steps succeeded
234+
if execution.Error != "" {
235+
t.Errorf("Expected empty error for successful branch execution, but got: %s", execution.Error)
236236
}
237237

238238
// Find the branch step regardless of ordering

core/taskengine/partial_success_test.go

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,9 @@ func TestAnalyzeExecutionResult_AllSuccess(t *testing.T) {
3939
},
4040
}
4141

42-
success, errorMessage, failedCount, resultStatus := vm.AnalyzeExecutionResult()
42+
errorMessage, failedCount, resultStatus := vm.AnalyzeExecutionResult()
4343

4444
// Verify results
45-
if !success {
46-
t.Errorf("Expected success=true, got success=%v", success)
47-
}
4845
if errorMessage != "" {
4946
t.Errorf("Expected empty error message, got: %s", errorMessage)
5047
}
@@ -56,8 +53,8 @@ func TestAnalyzeExecutionResult_AllSuccess(t *testing.T) {
5653
}
5754
}
5855

59-
// TestAnalyzeExecutionResult_PartialSuccess tests the case where some steps succeed and some fail
60-
func TestAnalyzeExecutionResult_PartialSuccess(t *testing.T) {
56+
// TestAnalyzeExecutionResult_SomeStepsFailed tests the case where some steps succeed and some fail
57+
func TestAnalyzeExecutionResult_SomeStepsFailed(t *testing.T) {
6158
vm := NewVM()
6259
vm.logger = testutil.GetLogger()
6360

@@ -89,20 +86,17 @@ func TestAnalyzeExecutionResult_PartialSuccess(t *testing.T) {
8986
},
9087
}
9188

92-
success, errorMessage, failedCount, resultStatus := vm.AnalyzeExecutionResult()
89+
errorMessage, failedCount, resultStatus := vm.AnalyzeExecutionResult()
9390

9491
// Verify results
95-
if success {
96-
t.Errorf("Expected success=false for partial success, got success=%v", success)
97-
}
9892
if errorMessage == "" {
99-
t.Errorf("Expected non-empty error message for partial success")
93+
t.Errorf("Expected non-empty error message when some steps failed")
10094
}
10195
if failedCount != 1 {
10296
t.Errorf("Expected failedCount=1, got failedCount=%d", failedCount)
10397
}
104-
if resultStatus != ExecutionPartialSuccess {
105-
t.Errorf("Expected resultStatus=ExecutionPartialSuccess, got resultStatus=%v", resultStatus)
98+
if resultStatus != ExecutionFailed {
99+
t.Errorf("Expected resultStatus=ExecutionFailed, got resultStatus=%v", resultStatus)
106100
}
107101

108102
// Check that error message contains failure information
@@ -139,20 +133,17 @@ func TestAnalyzeExecutionResult_AllFailure(t *testing.T) {
139133
},
140134
}
141135

142-
success, errorMessage, failedCount, resultStatus := vm.AnalyzeExecutionResult()
136+
errorMessage, failedCount, resultStatus := vm.AnalyzeExecutionResult()
143137

144138
// Verify results
145-
if success {
146-
t.Errorf("Expected success=false for all failures, got success=%v", success)
147-
}
148139
if errorMessage == "" {
149140
t.Errorf("Expected non-empty error message for all failures")
150141
}
151142
if failedCount != 3 {
152143
t.Errorf("Expected failedCount=3, got failedCount=%d", failedCount)
153144
}
154-
if resultStatus != ExecutionFailure {
155-
t.Errorf("Expected resultStatus=ExecutionFailure, got resultStatus=%v", resultStatus)
145+
if resultStatus != ExecutionFailed {
146+
t.Errorf("Expected resultStatus=ExecutionFailed, got resultStatus=%v", resultStatus)
156147
}
157148

158149
// Check that error message contains failure information
@@ -170,25 +161,22 @@ func TestAnalyzeExecutionResult_NoSteps(t *testing.T) {
170161
// No execution logs
171162
vm.ExecutionLogs = []*avsproto.Execution_Step{}
172163

173-
success, errorMessage, failedCount, resultStatus := vm.AnalyzeExecutionResult()
164+
errorMessage, failedCount, resultStatus := vm.AnalyzeExecutionResult()
174165

175166
// Verify results
176-
if success {
177-
t.Errorf("Expected success=false for no steps, got success=%v", success)
178-
}
179167
if errorMessage != "no execution steps found" {
180168
t.Errorf("Expected specific error message for no steps, got: %s", errorMessage)
181169
}
182170
if failedCount != 0 {
183171
t.Errorf("Expected failedCount=0 for no steps, got failedCount=%d", failedCount)
184172
}
185-
if resultStatus != ExecutionFailure {
186-
t.Errorf("Expected resultStatus=ExecutionFailure for no steps, got resultStatus=%v", resultStatus)
173+
if resultStatus != ExecutionFailed {
174+
t.Errorf("Expected resultStatus=ExecutionFailed for no steps, got resultStatus=%v", resultStatus)
187175
}
188176
}
189177

190-
// TestGetExecutionStatus_PartialSuccess tests the GetExecutionStatus method for partial success
191-
func TestGetExecutionStatus_PartialSuccess(t *testing.T) {
178+
// TestGetExecutionStatus_StepFailures tests the GetExecutionStatus method when some steps fail
179+
func TestGetExecutionStatus_StepFailures(t *testing.T) {
192180
// Set up test database and engine
193181
db := testutil.TestMustDB()
194182
defer storage.Destroy(db.(*storage.BadgerStorage))
@@ -209,13 +197,13 @@ func TestGetExecutionStatus_PartialSuccess(t *testing.T) {
209197
},
210198
}
211199

212-
// Create execution with partial success (some steps succeed, some fail)
200+
// Create execution where some steps succeed and some fail
213201
execution := &avsproto.Execution{
214202
Id: "test-execution-id",
215203
StartAt: time.Now().UnixMilli(),
216204
EndAt: time.Now().UnixMilli(),
217-
Status: avsproto.ExecutionStatus_EXECUTION_STATUS_PARTIAL_SUCCESS, // Overall status is partial success
218-
Error: "Partial success: 1 of 3 steps failed: Database Query",
205+
Status: avsproto.ExecutionStatus_EXECUTION_STATUS_FAILED,
206+
Error: "1 of 3 steps failed: Database Query",
219207
Index: 0, // First execution
220208
Steps: []*avsproto.Execution_Step{
221209
{
@@ -269,9 +257,9 @@ func TestGetExecutionStatus_PartialSuccess(t *testing.T) {
269257
t.Fatalf("GetExecutionStatus failed: %v", err)
270258
}
271259

272-
// Verify that it returns PARTIAL_SUCCESS status
273-
if statusResp.Status != avsproto.ExecutionStatus_EXECUTION_STATUS_PARTIAL_SUCCESS {
274-
t.Errorf("Expected EXECUTION_STATUS_PARTIAL_SUCCESS, got %v", statusResp.Status)
260+
// Verify that it returns FAILED status (some steps failed)
261+
if statusResp.Status != avsproto.ExecutionStatus_EXECUTION_STATUS_FAILED {
262+
t.Errorf("Expected EXECUTION_STATUS_FAILED, got %v", statusResp.Status)
275263
}
276264
}
277265

core/taskengine/vm.go

Lines changed: 20 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3208,12 +3208,12 @@ func (v *VM) createExecutionStep(nodeId string, success bool, errorMsg string, l
32083208
type ExecutionResultStatus int
32093209

32103210
const (
3211-
// ExecutionSuccess indicates all steps completed successfully
3211+
// ExecutionSuccess indicates all executed steps succeeded (includes branch/conditional skips)
32123212
ExecutionSuccess ExecutionResultStatus = iota
3213-
// ExecutionPartialSuccess indicates some steps succeeded but at least one failed
3214-
ExecutionPartialSuccess
3215-
// ExecutionFailure indicates execution failed (all steps failed or critical failure)
3216-
ExecutionFailure
3213+
// ExecutionFailed indicates one or more node-level steps failed during execution
3214+
ExecutionFailed
3215+
// ExecutionError indicates a system-level failure (VM could not run the workflow)
3216+
ExecutionError
32173217
)
32183218

32193219
// getStepDisplayName extracts the display name for a step, preferring the name over ID
@@ -3225,77 +3225,36 @@ func getStepDisplayName(step *avsproto.Execution_Step) string {
32253225
return stepName
32263226
}
32273227

3228-
// AnalyzeExecutionResult examines all execution steps and determines overall success/failure/partial status
3229-
// Returns (success, errorMessage, failedStepCount, resultStatus)
3230-
func (v *VM) AnalyzeExecutionResult() (bool, string, int, ExecutionResultStatus) {
3228+
// AnalyzeExecutionResult examines all execution steps and determines overall success/failure status.
3229+
// Returns (errorMessage, failedStepCount, resultStatus)
3230+
func (v *VM) AnalyzeExecutionResult() (string, int, ExecutionResultStatus) {
32313231
v.mu.Lock()
32323232
defer v.mu.Unlock()
32333233

32343234
if len(v.ExecutionLogs) == 0 {
3235-
return false, "no execution steps found", 0, ExecutionFailure
3235+
return "no execution steps found", 0, ExecutionFailed
32363236
}
32373237

32383238
var failedStepNames []string
3239-
var successfulStepNames []string
3240-
var firstErrorMessage string
32413239

32423240
for _, step := range v.ExecutionLogs {
3243-
stepName := getStepDisplayName(step)
3244-
32453241
if !step.Success && step.Error != "" {
3246-
if firstErrorMessage == "" {
3247-
firstErrorMessage = step.Error
3248-
}
3249-
failedStepNames = append(failedStepNames, stepName)
3250-
} else if step.Success {
3251-
successfulStepNames = append(successfulStepNames, stepName)
3242+
failedStepNames = append(failedStepNames, getStepDisplayName(step))
32523243
}
32533244
}
32543245

32553246
failedCount := len(failedStepNames)
3256-
totalSteps := len(v.ExecutionLogs)
3257-
3258-
// Determine execution status and success flag
3259-
var resultStatus ExecutionResultStatus
3260-
var success bool
3261-
var errorMessage string
32623247

32633248
if failedCount == 0 {
3264-
// All executed steps succeeded. However, if not all workflow steps executed
3265-
// (e.g., due to branch selections or conditional skips), report PARTIAL_SUCCESS
3266-
// to reflect that the workflow did not traverse all configured nodes.
3267-
executedCount := len(v.ExecutionLogs)
3268-
totalWorkflowSteps := 1 + len(v.TaskNodes) // 1 trigger + all nodes
3269-
if v.GetTaskId() == "" && len(v.TaskNodes) == 1 {
3270-
// single-node immediate execution
3271-
totalWorkflowSteps = 1
3272-
}
3273-
3274-
if executedCount < totalWorkflowSteps {
3275-
resultStatus = ExecutionPartialSuccess
3276-
success = false // do not mark full success when nodes were skipped
3277-
errorMessage = fmt.Sprintf("Partial execution: %d out of %d steps executed (branch/conditional path)", executedCount, totalWorkflowSteps)
3278-
} else {
3279-
// All steps that exist in the workflow executed and succeeded
3280-
resultStatus = ExecutionSuccess
3281-
success = true
3282-
errorMessage = ""
3283-
}
3284-
} else if failedCount > 0 {
3285-
// Distinguish between all failed vs some failed (for internal status tracking)
3286-
if failedCount == totalSteps {
3287-
// All steps failed
3288-
resultStatus = ExecutionFailure
3289-
} else {
3290-
// Some steps succeeded, some failed - partial success for internal tracking
3291-
resultStatus = ExecutionPartialSuccess
3292-
}
3293-
success = false
3294-
// Use simple error message format (no prefix) for both cases
3295-
errorMessage = formatExecutionErrorMessage("", failedCount, totalSteps, failedStepNames)
3249+
// All executed steps succeeded. Branch/conditional skips are normal
3250+
// workflow behavior and count as SUCCESS — the workflow did what it
3251+
// was configured to do.
3252+
return "", 0, ExecutionSuccess
32963253
}
32973254

3298-
return success, errorMessage, failedCount, resultStatus
3255+
// One or more steps failed (covers both partial and total failure).
3256+
errorMessage := formatExecutionErrorMessage("", failedCount, len(v.ExecutionLogs), failedStepNames)
3257+
return errorMessage, failedCount, ExecutionFailed
32993258
}
33003259

33013260
// CalculateTotalGasCost sums up gas costs from all execution steps that involve blockchain operations
@@ -3366,10 +3325,10 @@ func convertToExecutionStatus(resultStatus ExecutionResultStatus) avsproto.Execu
33663325
switch resultStatus {
33673326
case ExecutionSuccess:
33683327
return avsproto.ExecutionStatus_EXECUTION_STATUS_SUCCESS
3369-
case ExecutionPartialSuccess:
3370-
return avsproto.ExecutionStatus_EXECUTION_STATUS_PARTIAL_SUCCESS
3371-
case ExecutionFailure:
3328+
case ExecutionFailed:
33723329
return avsproto.ExecutionStatus_EXECUTION_STATUS_FAILED
3330+
case ExecutionError:
3331+
return avsproto.ExecutionStatus_EXECUTION_STATUS_ERROR
33733332
default:
33743333
return avsproto.ExecutionStatus_EXECUTION_STATUS_UNSPECIFIED
33753334
}

0 commit comments

Comments
 (0)