docs: public void signalEvent(String compensationType, Object activityRefStr) {
          if( activityRefStr == null || ! (activityRefStr instanceof String) ) { 
              throw new WorkflowRuntimeException(null, getProcessInstance(), 
                      "Compensation can only be triggered with String events, not an event of type " 
                      + activityRefStr == null ? "null" : activityRefStr.getClass().getSimpleName());
          }
         
          // 1. parse the activity ref (is it general or specific compensation?)
          String activityRef = (String) activityRefStr;
          String toCompensateNodeId = activityRef;
          boolean generalCompensation = false;
          if( activityRef.startsWith(IMPLICIT_COMPENSATION_PREFIX) ) {
              toCompensateNodeId = activityRef.substring(IMPLICIT_COMPENSATION_PREFIX.length());
              generalCompensation = true;
          } 
         
          org.jbpm.process.core.Process process = (org.jbpm.process.core.Process) instance.getProcess();
  
          // 2. for specific compensation: find the node that will be compensated
          //    for general compensation: find the compensation scope container that contains all the visible compensation handlers
          Node toCompensateNode = null;
          ContextContainer compensationScopeContainer = null;
          if( generalCompensation ) { 
              if( toCompensateNodeId.equals(instance.getProcessId()) ) {
                  compensationScopeContainer = process;
              } else { 
                  compensationScopeContainer = (ContextContainer) findNode(toCompensateNodeId);
              }
          } else { 
              toCompensateNode = findNode(toCompensateNodeId);
          }
          
          // 3. If the node exists, 
          //   a. find the node container for which the compensation handler is visible
          //   b. create the compensation scope instance
          //   c. handle the exception (which also cleans up the generated node instances)
          if( toCompensateNode != null || compensationScopeContainer != null ) { 
              CompensationScope compensationScope = null;
              if( compensationScopeContainer != null  ) { 
                  compensationScope = (CompensationScope) compensationScopeContainer.getDefaultContext(COMPENSATION_SCOPE);
              } else { 
                  compensationScope 
                      = (CompensationScope) ((NodeImpl) toCompensateNode).resolveContext(COMPENSATION_SCOPE, toCompensateNodeId);
              }
              assert compensationScope != null : "Compensation scope for node [" + toCompensateNodeId + "] could not be found!";
  
              CompensationScopeInstance scopeInstance;
              if( compensationScope.getContextContainerId().equals(process.getId()) ) {
                  // process level compensation
                  scopeInstance = (CompensationScopeInstance) instance.getContextInstance(compensationScope);
              } else { 
                  // nested compensation
                  Stack<NodeInstance> generatedInstances;
                  if( toCompensateNode == null ) { 
                      // logic is the same if it's specific or general
                      generatedInstances = createNodeInstanceContainers((Node) compensationScopeContainer, true);
                  } else { 
                      generatedInstances = createNodeInstanceContainers(toCompensateNode, false);
                  }
                  NodeInstance nodeInstanceContainer = generatedInstances.peek();
                  scopeInstance 
                      = ((CompensationScopeInstance) 
                              ((ContextInstanceContainer) nodeInstanceContainer).getContextInstance(compensationScope));
                  scopeInstance.addCompensationInstances(generatedInstances);
              }
              
              scopeInstance.handleException(activityRef, null);     
          }
      }