docs: public StepResultCapture executeWorkflowStep(
              final StepExecutionContext executionContext,
              final Map<Integer, StepExecutionResult> failedMap,
              final List<StepExecutionResult> resultList,
              final boolean keepgoing,
              final WorkflowExecutionListener wlistener,
              final int c,
              final StepExecutionItem cmd
      )
      {
          if (null != wlistener) {
              wlistener.beginWorkflowItem(c, cmd);
          }
          //collab
          WorkflowStatusResultImpl result = WorkflowStatusResultImpl.builder().success(false).build();
  
          //wrap node failed listener (if any) and capture status results
          NodeRecorder stepCaptureFailedNodesListener = new NodeRecorder();
  
          //create the new context for workflow execution
          ExecutionContextImpl.Builder wfRunContext = new ExecutionContextImpl.Builder(executionContext);
  
          replaceFailedNodesListenerInContext(
                  wfRunContext,
                  stepCaptureFailedNodesListener,
                  executionContext.getExecutionListener()
          );
  
  
          final FlowController stepController = new FlowController();
          wfRunContext.flowControl(stepController);
  
  
          final DataOutput outputContext = new DataOutput(ContextView.step(c));
          wfRunContext.outputContext(outputContext);
  
          ExecutionContextImpl wfRunContextBuilt = wfRunContext.build();
  
  
          //execute the step item, and store the results
  
          final Map<Integer, StepExecutionResult> stepFailedMap = new HashMap<>();
  
  
          StepExecutionResult stepResult = executeWFItem(
                  wfRunContextBuilt,
                  stepFailedMap,
                  c,
                  cmd
          );
  
  
          //node recorder report
          Map<String, NodeStepResult> nodeFailures = stepCaptureFailedNodesListener.getFailedNodes();
          reportNodesMatched(executionContext, stepCaptureFailedNodesListener);
  
          //collect node data results
          WFSharedContext combinedResultData = new WFSharedContext();
          combineResultData(c, outputContext, combinedResultData, stepResult);
  
          if (stepController.isControlled()) {
              result = WorkflowStatusResultImpl.with(stepController);
              stepResult = controlledStepResult(stepController, stepResult);
              executionContext.getExecutionListener().log(3, result.toString());
          }
  
          result.setSuccess(stepResult.isSuccess());
  
          try {
              if (!result.isSuccess() && cmd instanceof HasFailureHandler) {
                  final HasFailureHandler handles = (HasFailureHandler) cmd;
                  final StepExecutionItem handler = handles.getFailureHandler();
                  if (null != handler) {
                      //if there is a failure, and a failureHandler item, execute the failure handler
                      //set keepgoing=false, and store the results
                      //will throw an exception on failure because keepgoing=false
  
                      NodeRecorder handlerCaptureFailedNodesListener = new NodeRecorder();
  
                      ExecutionContextImpl.Builder wfHandlerContext = new ExecutionContextImpl.Builder(executionContext);
                      replaceFailedNodesListenerInContext(
                              wfHandlerContext,
                              handlerCaptureFailedNodesListener,
                              executionContext.getExecutionListener()
                      );
  
                      //if multi-node, determine set of nodes to run handler on: (failed node list only)
                      if (stepCaptureFailedNodesListener.getMatchedNodes().size() > 1) {
                          HashSet<String> failedNodeList = new HashSet<>(
                                  stepCaptureFailedNodesListener.getFailedNodes().keySet());
  
                          wfHandlerContext.nodeSelector(SelectorUtils.nodeList(failedNodeList));
  
                      }
  
                      //add step failure data to data context
                      addStepFailureContextData(stepResult, wfHandlerContext);
  
                      //extract node-specific failure and set as node-context data
                      addNodeStepFailureContextData(stepResult, wfHandlerContext);
  
                      //add in data context results produced by the step
                      wfHandlerContext.mergeSharedContext(combinedResultData);
  
                      //allow flow control
                      final FlowController handlerController = new FlowController();
                      wfHandlerContext.flowControl(handlerController);
  
                      wfHandlerContext.outputContext(outputContext);
  
                      Map<Integer, StepExecutionResult> handlerFailedMap = new HashMap<>();
  
                      if (null != wlistener) {
                          wlistener.beginWorkflowItemErrorHandler(c, cmd);
                      }
  
                      StepExecutionResult handlerResult = executeWFItem(
                              wfHandlerContext.build(),
                              handlerFailedMap,
                              c,
                              handler
                      );
                      boolean handlerSuccess = handlerResult.isSuccess();
  
                      if (null != wlistener) {
                          wlistener.finishWorkflowItemErrorHandler(c, cmd, handlerResult);
                      }
  
                      //combine handler result data
                      combineResultData(c, outputContext, combinedResultData, handlerResult);
  
                      if (handlerController.isControlled() &&
                          handlerController.getControlBehavior() == ControlBehavior.Halt) {
                          //handler called Halt()
                          result = WorkflowStatusResultImpl.with(handlerController);
                          executionContext.getExecutionListener().log(3, result.toString());
                      } else {
  
                          //handle success conditions:
                          //1. if keepgoing=true, then status from handler overrides original step
                          //2. keepgoing=false, then status is the same as the original step, unless
                          //   the keepgoingOnSuccess is set to true and the handler succeeded
                          boolean useHandlerResults = keepgoing;
                          if (!keepgoing && handlerSuccess && handler instanceof HandlerExecutionItem) {
                              useHandlerResults = ((HandlerExecutionItem) handler).isKeepgoingOnSuccess();
                          }
                          if (useHandlerResults) {
                              result.setSuccess(handlerSuccess);
                              stepResult = handlerResult;
                              stepFailedMap.clear();
                              stepFailedMap.putAll(handlerFailedMap);
                              nodeFailures = handlerCaptureFailedNodesListener.getFailedNodes();
                          }
                      }
                  }
              }
          } catch (RuntimeException t) {
  //            t.printStackTrace(System.err);
              stepResult = new StepExecutionResultImpl(t, StepFailureReason.Unknown, t.getMessage());
              throw t;
          } finally {
              if (null != wlistener) {
                  wlistener.finishWorkflowItem(c, cmd, stepResult);
              }
          }
          //report data
          resultList.add(stepResult);
          failedMap.putAll(stepFailedMap);
  
          //report node failures based on results of step and handler run.
          if (null != executionContext.getExecutionListener() && null != executionContext.getExecutionListener()
                                                                                         .getFailedNodesListener()) {
              if (nodeFailures.size() > 0) {
                  executionContext.getExecutionListener().getFailedNodesListener().nodesFailed(
                          nodeFailures);
              } else if (result.isSuccess()) {
                  executionContext.getExecutionListener().getFailedNodesListener().nodesSucceeded();
              }
  
          }
  
          return new StepResultCapture(stepResult, result, combinedResultData);
      }