From cd881b0d6412fc84f1a77ac5fbe192a89513aa30 Mon Sep 17 00:00:00 2001 From: Wei Lin Date: Mon, 13 Apr 2026 16:28:40 -0700 Subject: [PATCH 1/4] docs: add SDK changes section to execution status changelog Document the ava-sdk-js TypeScript enum update, backward compat handling for legacy proto value 4, and test migration scope. --- .../changes/0001-execution-status-redesign.md | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/docs/changes/0001-execution-status-redesign.md b/docs/changes/0001-execution-status-redesign.md index dd90efe2..84a98adf 100644 --- a/docs/changes/0001-execution-status-redesign.md +++ b/docs/changes/0001-execution-status-redesign.md @@ -82,6 +82,45 @@ enum ExecutionStatus { 4. Treat `ERROR` as a system-level problem (not caused by the workflow configuration itself). +## SDK Changes (ava-sdk-js) + +**PR**: https://github.com/AvaProtocol/ava-sdk-js/pull/212 + +### TypeScript enum update + +```typescript +// packages/types/src/enums.ts +export enum ExecutionStatus { + Unspecified = "unspecified", + Pending = "pending", + Success = "success", + Failed = "failed", + Error = "error", // NEW — replaces PartialSuccess +} +``` + +### Backward compatibility + +The SDK conversion functions handle legacy proto value `4` (retired +`PARTIAL_SUCCESS`) by mapping it to `ExecutionStatus.Failed`: + +```typescript +case ProtobufExecutionStatus.EXECUTION_STATUS_FAILED: +case 4 as ProtobufExecutionStatus: // legacy PARTIAL_SUCCESS + return ExecutionStatus.Failed; +``` + +This ensures stored executions written before the migration are +displayed correctly without requiring a data migration. + +### Test updates (18 files) + +- All `ExecutionStatus.PartialSuccess` assertions replaced with + `ExecutionStatus.Failed` (step failures) or `ExecutionStatus.Success` + (branch skips) +- `partialSuccess.test.ts` rewritten — test names and expectations + aligned with the new three-value model + ## Consequences - Branch-skip workflows stop surfacing as warnings in the UI. @@ -90,3 +129,5 @@ enum ExecutionStatus { - Email summaries for branch-skip workflows now show a green success badge with a note like "3 nodes skipped by Branch condition" instead of a yellow warning badge. +- Legacy stored executions with proto value `4` are transparently + mapped to `Failed` by the SDK — no data migration required. From 079d9559076143b05273a34075b137a76a1c906b Mon Sep 17 00:00:00 2001 From: Wei Lin Date: Mon, 13 Apr 2026 16:29:52 -0700 Subject: [PATCH 2/4] fix: loop node GraphQL runner never extracts iteration results (#523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extractResultData in the execution queue was missing the GraphQL output case. GraphQL HTTP requests executed successfully and steps were marked Success=true, but the result data was never extracted — returning nil. The loop aggregation then counted all nil results as failed iterations, producing "2 of 2 iterations failed" even though the queries succeeded. Add the missing step.GetGraphql() case to extractResultData and to the debug logging switch. --- core/taskengine/vm.go | 5 ++ .../vm_runner_graphql_query_test.go | 89 +++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/core/taskengine/vm.go b/core/taskengine/vm.go index cbbfb4f0..8443c887 100644 --- a/core/taskengine/vm.go +++ b/core/taskengine/vm.go @@ -4188,6 +4188,8 @@ func (eq *ExecutionQueue) executeTask(task *ExecutionTask) *ExecutionResult { nodeType = "customCode" case step.GetRestApi() != nil: nodeType = "restApi" + case step.GetGraphql() != nil: + nodeType = "graphql" } eq.vm.logger.Info("Loop iteration result extracted", "step_id", task.StepID, @@ -4253,6 +4255,9 @@ func (eq *ExecutionQueue) extractResultData(step *avsproto.Execution_Step) inter txHash := extractTxHashFromFlatMetadata(step.Metadata) return wrapResultWithTxHash(resultData, txHash) } + if graphqlOutput := step.GetGraphql(); graphqlOutput != nil && graphqlOutput.Data != nil { + return graphqlOutput.Data.AsInterface() + } return nil } diff --git a/core/taskengine/vm_runner_graphql_query_test.go b/core/taskengine/vm_runner_graphql_query_test.go index dd53118b..1445dd50 100644 --- a/core/taskengine/vm_runner_graphql_query_test.go +++ b/core/taskengine/vm_runner_graphql_query_test.go @@ -171,3 +171,92 @@ func TestGraphlQlNodeSimpleQuery(t *testing.T) { t.Errorf("decimals doesn't match. expected %d got %d", 6, output.Markets[1].InputToken.Decimals) } } + +// Test that Loop node with GraphQL runner correctly extracts iteration results. +// This is a regression test for https://github.com/AvaProtocol/EigenLayer-AVS/issues/523 +// where extractResultData was missing the GraphQL output case, causing all GraphQL +// loop iterations to be counted as "failed" even though the HTTP requests succeeded. +func TestLoopWithGraphQLRunner(t *testing.T) { + requestCount := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestCount++ + var body map[string]interface{} + json.NewDecoder(r.Body).Decode(&body) + + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, `{ + "data": { + "country": { + "name": "TestCountry", + "code": "TC" + } + } + }`) + })) + defer mockServer.Close() + + // Build a loop node with GraphQL runner + loopConfig := map[string]interface{}{ + "inputVariable": "{{countryCodes}}", + "iterVal": "value", + "iterKey": "index", + "runner": map[string]interface{}{ + "type": "graphqlDataQuery", + "config": map[string]interface{}{ + "url": mockServer.URL, + "query": `query { country(code: "{{value}}") { name code } }`, + }, + }, + } + + node, err := CreateNodeFromType(NodeTypeLoop, loopConfig, "loop_graphql_test") + if err != nil { + t.Fatalf("failed to create loop node: %v", err) + } + + inputVariables := map[string]interface{}{ + "countryCodes": []interface{}{"US", "JP"}, + } + + vm, err := NewVMWithData(nil, nil, testutil.GetTestSmartWalletConfig(), nil) + if err != nil { + t.Fatalf("failed to create VM: %v", err) + } + vm.WithLogger(testutil.GetLogger()) + + step, err := vm.RunNodeWithInputs(node, inputVariables) + if err != nil { + t.Fatalf("expected loop to succeed but got error: %v", err) + } + + if !step.Success { + t.Fatalf("expected loop step to succeed but got failure: %s", step.Error) + } + + // Verify GraphQL HTTP requests were actually made + if requestCount != 2 { + t.Errorf("expected 2 GraphQL HTTP requests but got %d", requestCount) + } + + // Verify loop output data contains results from both iterations + loopOutput := step.GetLoop() + if loopOutput == nil || loopOutput.Data == nil { + t.Fatal("expected loop output data but got nil") + } + + outputArray, ok := loopOutput.Data.AsInterface().([]interface{}) + if !ok { + t.Fatalf("expected loop output to be an array, got %T", loopOutput.Data.AsInterface()) + } + + if len(outputArray) != 2 { + t.Fatalf("expected 2 results but got %d", len(outputArray)) + } + + // Verify each iteration result is non-nil (the fix for issue #523) + for i, result := range outputArray { + if result == nil { + t.Errorf("iteration %d result is nil - extractResultData is not handling GraphQL output", i) + } + } +} From 7e4cfe83ddb07c1e24ce0eecedb2da0a232a5db5 Mon Sep 17 00:00:00 2001 From: Wei Lin Date: Mon, 13 Apr 2026 16:36:03 -0700 Subject: [PATCH 3/4] docs: add comprehensive client interpretation guide for execution status redesign Documents the full decision tree for how Studio interprets execution status both with the current SDK (PartialSuccess workaround) and the target state after the SDK upgrade. Includes per-component mapping of where status logic lives and a migration checklist. --- .../changes/0001-execution-status-redesign.md | 109 ++++++++++++++++-- 1 file changed, 100 insertions(+), 9 deletions(-) diff --git a/docs/changes/0001-execution-status-redesign.md b/docs/changes/0001-execution-status-redesign.md index 84a98adf..8848af06 100644 --- a/docs/changes/0001-execution-status-redesign.md +++ b/docs/changes/0001-execution-status-redesign.md @@ -72,15 +72,106 @@ enum ExecutionStatus { - Enum value `4` is reserved and will not be reused. - New enum value `ERROR = 5` for system-level failures. -## SDK/Client Migration - -1. Remove any `PartialSuccess` handling or `isConditionalSkip` workarounds. -2. Treat `SUCCESS` as the only positive outcome. Branch skips no longer - produce a warning status. -3. Treat `FAILED` as the single status for any node-level execution failure, - regardless of whether some or all steps failed. -4. Treat `ERROR` as a system-level problem (not caused by the workflow - configuration itself). +## How the Client Should Interpret Execution Status + +### Current state (SDK still has `PartialSuccess`) + +The SDK (`@avaprotocol/types`) still exposes four runtime statuses: +`Success`, `PartialSuccess`, `Failed`, `Pending`. The backend returns +`partialSuccess` for **both** conditional branch skips and actual step +failures, so the client must disambiguate. + +**Decision tree for `PartialSuccess`:** + +``` +status === PartialSuccess + └─ steps.every(s => s.success) ? + ├─ YES → conditional skip (treat as success) + └─ NO → real step failure (treat as error/warning) +``` + +**Full client interpretation table (current SDK):** + +| `status` | `steps.every(s => s.success)` | Interpretation | UI treatment | +|------------------|-------------------------------|------------------------|----------------------------------------------| +| `Success` | true | All nodes ran, all OK | Green — "Simulation completed" | +| `PartialSuccess` | true | Branch skipped nodes | Green — "Simulation completed — Some steps skipped by condition" | +| `PartialSuccess` | false | Real step failure(s) | Yellow — "Simulation completed with errors" | +| `Failed` | false | All-or-nothing failure | Red — "Simulation completed with errors" | +| `Pending` | n/a | Still running | Neutral — loading state | + +**Where this logic lives in Studio today:** + +1. **Simulation status label** (`components/CanvasToolbarBottom/index.tsx`): + ```ts + const isSuccess = simulationResult?.status === ExecutionStatus.Success; + const isFailed = simulationResult?.status === ExecutionStatus.Failed; + const isConditionalSkip = + simulationResult?.status === ExecutionStatus.PartialSuccess && + simulationResult?.steps?.every((step: StepProps) => step.success); + const hasStepErrors = !isSuccess && !isConditionalSkip; + ``` + - `isConditionalSkip` → green label, normal "Deploy" button + - `hasStepErrors` → yellow/red label, "Deploy Anyway" button with caution tooltip + +2. **Simulation gate for deploy** (`app/types/simulation.ts`): + Only `ExecutionStatus.Success` passes the gate. `PartialSuccess` (even + conditional skips) currently fails the gate — this needs updating after + the SDK change so that the gate also passes for `Success` with skipped + steps. + +3. **Execution time display** (`CanvasToolbarBottom getExecutionTimeDisplay`): + Maps status to text color: `Success` → green, `PartialSuccess` → yellow, + `Failed` → red, `Pending` → yellow. + +4. **Execution history chips** (`components/workflows/StatusChips.tsx`): + - `Success` → "Completed" (green check) + - `PartialSuccess` → "Partial Success" (yellow alert) + - `Failed` → "Has Error" (red X) + +5. **Execution modal header** (`components/workflows/ExecutionModal.tsx`): + - `Success` → green "Success" + - `PartialSuccess` → yellow "Partial Success" + - `Failed` → red "Has Error" + +6. **Manual run callback** (`components/workflows/WorkflowControlButton.tsx`): + Treats both `Success` and `PartialSuccess` as a successful run for + the toast notification. This is the only place that already collapses + the two into a single positive path. + +### Target state (after SDK upgrade) + +Once the SDK ships the `SUCCESS`/`FAILED`/`ERROR` redesign: + +| `status` | Interpretation | UI treatment | +|-----------|-----------------------------------------|---------------------------------------------| +| `Success` | All executed steps passed (skips OK) | Green — "Simulation completed" | +| `Failed` | At least one step failed | Red — "Simulation completed with errors" | +| `Error` | System-level failure (VM crash, etc.) | Red — "Simulation failed — system error" | +| `Pending` | Still running | Neutral — loading state | + +**Branch skip detection** moves from status-level to the `steps` array: +compare `steps.length` against the workflow's total node count. If +`steps.length < nodeCount` and `status === Success`, display an +informational note like "N steps skipped by condition". + +### SDK/Client Migration Checklist + +After upgrading `@avaprotocol/types`: + +1. **Remove `isConditionalSkip` workaround** in `CanvasToolbarBottom` — `Success` + already covers this case. +2. **Remove `PartialSuccess` branches** from `StatusChips.tsx`, + `ExecutionModal.tsx`, `getExecutionTimeDisplay()`. +3. **Add `Error` handling** — new status for system-level failures. + Display distinctly from `Failed` (e.g., "System error — contact support" + vs "Execution failed — check step details"). +4. **Update simulation gate** (`app/types/simulation.ts`) — `Success` is the + only passing status (same as today, but no ambiguity). +5. **Update `WorkflowControlButton`** — remove `PartialSuccess` from the + success path; add `Error` to the failure path. +6. **Update Storybook mocks** — replace `ExecutionStatus.PartialSuccess` + references with `Failed` or `Success` as appropriate. ## SDK Changes (ava-sdk-js) From 477a0e87138878b042ef866710954b2b4f0b5f35 Mon Sep 17 00:00:00 2001 From: Wei Lin Date: Mon, 13 Apr 2026 16:37:48 -0700 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20address=20review=20feedback=20?= =?UTF-8?q?=E2=80=94=20remove=20unused=20decode,=20add=20ethTransfer=20to?= =?UTF-8?q?=20logging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove unused request body decode in test mock handler - Add missing ethTransfer case to loop iteration debug logging switch --- core/taskengine/vm.go | 2 ++ core/taskengine/vm_runner_graphql_query_test.go | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/core/taskengine/vm.go b/core/taskengine/vm.go index 8443c887..fc7a8973 100644 --- a/core/taskengine/vm.go +++ b/core/taskengine/vm.go @@ -4190,6 +4190,8 @@ func (eq *ExecutionQueue) executeTask(task *ExecutionTask) *ExecutionResult { nodeType = "restApi" case step.GetGraphql() != nil: nodeType = "graphql" + case step.GetEthTransfer() != nil: + nodeType = "ethTransfer" } eq.vm.logger.Info("Loop iteration result extracted", "step_id", task.StepID, diff --git a/core/taskengine/vm_runner_graphql_query_test.go b/core/taskengine/vm_runner_graphql_query_test.go index 1445dd50..24d50991 100644 --- a/core/taskengine/vm_runner_graphql_query_test.go +++ b/core/taskengine/vm_runner_graphql_query_test.go @@ -180,9 +180,6 @@ func TestLoopWithGraphQLRunner(t *testing.T) { requestCount := 0 mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requestCount++ - var body map[string]interface{} - json.NewDecoder(r.Body).Decode(&body) - w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, `{ "data": {