Skip to content

[BE][TM] Support Workflow Node Outcome in TaskManager #175

@sthanikan2000

Description

@sthanikan2000

Problem

The workflow node model now supports an optional outcome field to track completion sub-states (e.g., "APPROVED", "REJECTED", "EXPEDITED", "FAST_TRACKED") as part of the conditional unlock configuration feature (#174).

However, the TaskManager plugin system is not wired to populate this field. Currently:

  • plugin.ExecutionResponse has no Outcome field, so plugins cannot return outcomes
  • notifyWorkflowManager() does not accept or pass the outcome parameter
  • WorkflowManagerNotification has the Outcome field but it's never populated
  • The workflow state machine cannot evaluate unlock conditions that depend on node outcomes

This means even though the state machine supports outcome-based unlocking, there's no mechanism for task plugins to produce and communicate outcomes back to the workflow manager.

Proposed Solution

Wire up the Outcome field through the plugin execution pipeline:

1. Update plugin.ExecutionResponse

Add optional Outcome field to allow plugins to return completion outcomes:

type ExecutionResponse struct {
	NewState            *State
	ExtendedState       *string
	Outcome             *string  // NEW: Outcome sub-state for COMPLETED transitions (e.g., "APPROVED", "REJECTED")
	AppendGlobalContext map[string]any
	Message             string
	ApiResponse         *ApiResponse
}

2. Update TaskManager execute() method

Pass the outcome from ExecutionResponse to notifyWorkflowManager():

func (tm *taskManager) execute(ctx context.Context, activeTask *container.Container, payload *plugin.ExecutionRequest) (*plugin.ExecutionResponse, error) {
	result, err := activeTask.Execute(ctx, payload)
	if err != nil {
		return nil, err
	}

	if result.NewState != nil {
		tm.notifyWorkflowManager(ctx, activeTask.TaskID, result.NewState, result.ExtendedState, result.AppendGlobalContext, result.Outcome)  // NEW: pass outcome
	}

	return result, nil
}

3. Update notifyWorkflowManager() signature

Add outcome parameter and populate the notification:

func (tm *taskManager) notifyWorkflowManager(ctx context.Context, taskID uuid.UUID, state *plugin.State, extendedState *string, appendGlobalContext map[string]any, outcome *string) {
	// ... existing code ...
	notification := WorkflowManagerNotification{
		TaskID:              taskID,
		UpdatedState:        state,
		ExtendedState:       extendedState,
		AppendGlobalContext: appendGlobalContext,
		Outcome:             outcome,  // NEW: set outcome
	}
	// ... rest of implementation ...
}

4. Update all notifyWorkflowManager() call sites

Two locations in manager.go need updating:

  • Line ~272 (in execute())
  • Line ~286 (in container/manager interaction)

Add result.Outcome parameter to both calls.

5. Update Tests

Enhance manager_test.go to verify:

  • Outcomes are properly passed through ExecutionResponse → notification
  • Nil outcomes don't break the pipeline
  • Different outcome values (APPROVED, REJECTED, etc.) are correctly propagated

Alternatives

  1. Keep outcome in task plugin but don't propagate to workflow - Current state, but outcome information is lost before reaching state machine
  2. Use task extended_state for outcomes - Possible but conflates two concerns: extended error state vs. business outcome
  3. Add separate outcome notification channel - Over-engineered for current needs; reuse the existing notification mechanism

Acceptance Criteria

  • Outcome *string field added to plugin.ExecutionResponse
  • notifyWorkflowManager() accepts and passes outcome parameter
  • All call sites updated to pass result.Outcome
  • WorkflowManagerNotification.Outcome is populated during notification
  • Unit tests cover outcome propagation (happy path, nil outcome, various outcome values)
  • No breaking changes to plugin API (field is optional)
  • Existing plugins continue working unchanged

Files to Modify

  1. ./backend/internal/task/plugin/plugin.go - Add Outcome field to ExecutionResponse
  2. ./backend/internal/task/manager/manager.go - Update execute() and notifyWorkflowManager()
  3. ./backend/internal/task/manager/manager_test.go - Add outcome propagation tests

Implementation Notes

  • Keep Outcome as optional (*string) for backward compatibility
  • Nil outcomes should be handled gracefully (existing behavior)
  • Outcomes should propagate regardless of state value (but typically set on COMPLETED states)
  • No database schema changes required (outcome already added in WorkflowNode in [BE][WM] Conditional Workflow Unlocking and End-Node Completion #174)
  • Documentation: Update plugin developer guide to document when/how to set outcomes

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions