diff --git a/includes/Entity/Workflow.php b/includes/Entity/Workflow.php
index 3a5db25..3aec715 100644
--- a/includes/Entity/Workflow.php
+++ b/includes/Entity/Workflow.php
@@ -106,9 +106,6 @@ class Workflow extends Entity {
    *
    * This also handles importing, rebuilding, reverting from Features,
    * as defined in workflow.features.inc.
-   * todo: reverting does not refresh States and transitions, since no
-   * machine_name was present. As of 7.x-2.3, the machine_name exists in
-   * Workflow and WorkflowConfigTransition, so rebuilding is possible.
    *
    * When changing this function, test with the following situations:
    * - maintain Workflow in Admin UI;
@@ -123,6 +120,12 @@ class Workflow extends Entity {
     $is_rebuild = !empty($this->is_rebuild);
     $is_reverted = !empty($this->is_reverted);
 
+    if ($is_new || $is_rebuild || $is_reverted) {
+      // Unpack from exported format, if necessary
+      $state_name_to_id_map = $this->unpackStates();
+      $this->unpackTransitions($state_name_to_id_map);
+    }
+
     // If rebuild by Features, make some conversions.
     if (!$is_rebuild && !$is_reverted) {
       // Avoid troubles with features clone/revert/..
@@ -130,22 +133,40 @@ class Workflow extends Entity {
     }
     else {
       $role_map = isset($this->system_roles) ? $this->system_roles : array();
-      if ($role_map) {
+
+      if (!empty($role_map)) {
         // Remap roles. They can come from another system with shifted role IDs.
         // See also https://drupal.org/node/1702626 .
         $this->tab_roles = _workflow_rebuild_roles($this->tab_roles, $role_map);
+
         foreach ($this->transitions as &$transition) {
           $transition['roles'] = _workflow_rebuild_roles($transition['roles'], $role_map);
         }
       }
 
       // Insert the type_map when building from Features.
-      if ($this->typeMap) {
+      if (!empty($this->typeMap)) {
         foreach ($this->typeMap as $node_type) {
           workflow_insert_workflow_type_map($node_type, $this->wid);
         }
       }
+
+      // Delete all existing transitions.
+      if (!empty($this->name)) {
+        $old_workflow = workflow_load_by_name($this->name);
+
+        if (!empty($old_workflow)) {
+          /* @var Workflow $old_workflow */
+          $old_transitions = $old_workflow->getTransitions();
+
+          foreach ($old_transitions as $transition) {
+            /* @var WorkflowConfigTransition $transition */
+            $transition->delete();
+          }
+        }
+      }
     }
+
     // After update.php or import feature, label might be empty. @todo: remove in D8.
     if (empty($this->label)) {
       $this->label = $this->name;
@@ -153,36 +174,15 @@ class Workflow extends Entity {
 
     $return = parent::save();
 
-    // If a workflow is cloned in Admin UI, it contains data from original workflow.
-    // Redetermine the keys.
-    if (($is_new) && $this->states) {
-      foreach ($this->states as $state) {
-        // Can be array when cloning or with features.
-        $state = is_array($state) ? new WorkflowState($state) : $state;
-        // Set up a conversion table, while saving the states.
-        $old_sid = $state->sid;
-        $state->wid = $this->wid;
-        // @todo: setting sid to FALSE should be done by entity_ui_clone_entity().
-        $state->sid = FALSE;
-        $state->save();
-        $sid_conversion[$old_sid] = $state->sid;
-      }
+    if (($is_new || $is_rebuild || $is_reverted) && !empty($this->states)) {
+      // If a workflow is cloned in Admin UI, it contains data from original workflow.
+      // Redetermine the keys.
+      $sid_conversion = $this->saveStates();
 
       // Reset state cache.
       $this->getStates(TRUE, TRUE);
-      foreach ($this->transitions as &$transition) {
-        // Can be array when cloning or with features.
-        $transition = is_array($transition) ? new WorkflowConfigTransition($transition, 'WorkflowConfigTransition') : $transition;
-        // Convert the old sids of each transitions before saving.
-        // @todo: is this be done in 'clone $transition'?
-        // (That requires a list of transitions without tid and a wid-less conversion table.)
-        if (isset($sid_conversion[$transition->sid])) {
-          $transition->tid = FALSE;
-          $transition->sid = $sid_conversion[$transition->sid];
-          $transition->target_sid = $sid_conversion[$transition->target_sid];
-          $transition->save();
-        }
-      }
+
+      $this->saveTransitions($sid_conversion);
     }
 
     // Make sure a Creation state exists.
@@ -196,6 +196,182 @@ class Workflow extends Entity {
   }
 
   /**
+   * Unpacks states from the format used for export into the native, internal
+   * format.
+   *
+   * If the states in this object are currently organized by machine name
+   * rather than a numeric SID, this method assigns each state a distinct SID
+   * and then re-organizes the state map.
+   *
+   * The SIDs generated by this method cannot be assumed to be unique on the
+   * site, but provide a starting point for the save() method to correlate
+   * states and transitions when saving and assigning unique SIDs.
+   *
+   * The associative array that is returned can be used to unpack transitions.
+   *
+   * If the states in this object are already identified by a numeric SID,
+   * this method has no effect.
+   *
+   * @return array
+   *   Either an associative array of state machine names to SIDs; or, an empty
+   *   array if this object was already using numeric SIDs.
+   */
+  protected function unpackStates() {
+    if (!isset($this->states)) {
+      return array();
+    }
+
+    $machine_name_to_sid_map = array();
+
+    reset($this->states);
+
+    $sid_counter = 1;
+
+    // Skip unpack if the states of this object are already organized by
+    // numeric SID instead of machine name.
+    if (!empty($this->states) && !is_numeric(key($this->states))) {
+      $internal_states = array();
+
+      // NOTE: At this point, each state is an array, not yet an object.
+      foreach ($this->states as $machine_name => $state) {
+        $name         = $state['name'];
+        $state['sid'] = $sid_counter;
+
+        $internal_states[$sid_counter]  = $state;
+        $machine_name_to_sid_map[$name] = $sid_counter;
+
+        ++$sid_counter;
+      }
+
+      $this->states = $internal_states;
+    }
+
+    return $machine_name_to_sid_map;
+  }
+
+  /**
+   * Unpacks transitions from the format used for export into the internal format.
+   *
+   * If the transitions in this object are currently mapped to states by machine
+   * name rather than a numeric SID, this method uses the provided map to tie
+   * each transition to states by numeric SID.
+   *
+   * The SIDs used by the resulting transitions cannot be assumed to be unique
+   * on the site, but provide a starting point for the save() method
+   * to correlate states and transitions when saving.
+   *
+   * If the provided map is empty, this method has no effect.
+   *
+   * @param array $name_to_sid_map
+   *   An associative array with the machine names of states as the keys and the
+   *   numeric SIDs of each state as the values.
+   */
+  protected function unpackTransitions(array $name_to_sid_map) {
+    if (!empty($name_to_sid_map)) {
+      $internal_transitions = array();
+
+      // NOTE: At this point, each transition is an array, not yet an object.
+      foreach ($this->transitions as $transition) {
+        if (isset($transition['start_state']) &&
+            isset($transition['end_state'])) {
+          $start_state_name = $transition['start_state'];
+          $end_state_name   = $transition['end_state'];
+
+          if (isset($name_to_sid_map[$start_state_name]) &&
+              isset($name_to_sid_map[$end_state_name])) {
+            $start_id = $name_to_sid_map[$start_state_name];
+            $end_id   = $name_to_sid_map[$end_state_name];
+
+            $transition['sid']        = $start_id;
+            $transition['target_sid'] = $end_id;
+
+            unset($transition['start_state']);
+            unset($transition['end_state']);
+
+            $internal_transitions[] = $transition;
+          }
+        }
+      }
+
+      $this->transitions = $internal_transitions;
+    }
+  }
+
+  /**
+   * Saves the states of this workflow.
+   *
+   * States are matched to existing states in the database by machine name, and
+   * SIDs are automatically remapped, if necessary.
+   *
+   * @return array
+   *   An associative array with the old state IDs (SID) as keys and the new
+   *   state IDs as values.
+   */
+  protected function saveStates() {
+    $sid_conversion = array();
+
+    foreach ($this->states as $state) {
+      // Can be array when cloning or with features.
+      $state = is_array($state) ? new WorkflowState($state) : $state;
+
+      // Set up a conversion table, while saving the states.
+      $old_sid = $state->sid;
+
+      if (!empty($state->name)) {
+        $existing_state = workflow_state_load_by_name($state->name, $this->wid);
+
+        if (!empty($existing_state)) {
+          /* @var WorkflowState $existing_state */
+
+          // Update existing entity.
+          $state->sid         = $existing_state->sid;
+          $state->is_new      = FALSE;
+          $state->is_reverted = TRUE;
+        }
+        else {
+          // @todo: setting sid to FALSE should be done by entity_ui_clone_entity().
+          $state->sid = FALSE;
+        }
+      }
+
+      $state->wid = $this->wid;
+      $state->save();
+
+      $sid_conversion[$old_sid] = $state->sid;
+    }
+
+    return $sid_conversion;
+  }
+
+  /**
+   * Saves the transitions of this workflow.
+   *
+   * Transitions are optionally matched to existing transitions in the database
+   * by machine name, and TIDs are automatically remapped, if necessary.
+   *
+   * @param array $sid_conversion
+   *   An associative array that maps the SIDs in the transitions to their
+   *   new SIDs in the database.
+   */
+  protected function saveTransitions(array $sid_conversion, array $old_transitions = NULL) {
+    foreach ($this->transitions as &$transition) {
+      // Can be array when cloning or with features.
+      $transition = is_array($transition) ? new WorkflowConfigTransition($transition, 'WorkflowConfigTransition') : $transition;
+
+      // Convert the old sids of each transitions before saving.
+      // @todo: is this be done in 'clone $transition'?
+      // (That requires a list of transitions without tid and a wid-less conversion table.)
+      if (isset($sid_conversion[$transition->sid])) {
+        // @todo: setting sid to FALSE should be done by entity_ui_clone_entity().
+        $transition->tid = FALSE;
+        $transition->sid        = $sid_conversion[$transition->sid];
+        $transition->target_sid = $sid_conversion[$transition->target_sid];
+        $transition->save();
+      }
+    }
+  }
+
+  /**
    * Given a wid, delete the workflow and its data.
    *
    * @deprecated: workflow_delete_workflows_by_wid() --> Workflow::delete().
diff --git a/workflow.features.inc b/workflow.features.inc
index 168322b..2b21263 100644
--- a/workflow.features.inc
+++ b/workflow.features.inc
@@ -37,6 +37,7 @@ class WorkflowFeaturesController extends EntityDefaultFeaturesController {
         $export['features']['Workflow'][$workflow_name] = $workflow_name;
       }
     }
+
     return $pipe;
   }
 
@@ -50,31 +51,14 @@ class WorkflowFeaturesController extends EntityDefaultFeaturesController {
     $translatables = $code = array();
     $code[] = '  $workflows = array();';
     $code[] = '';
+
     foreach ($data as $identifier) {
       // Clone workflow to make sure changes are not propagated to original.
       if ($workflow = entity_load_single($this->type, $identifier)) {
-        // Make sure data is not copied to the database.
-        $workflow = clone $workflow;
-        // Modification for the Workflow object:
-        // For mapping workflow_field, add original wid in case target system
-        // already contains workflows.
-        $workflow->wid_original = $workflow->wid;
-        // Add roles to translate role IDs on target system.
-        $permission = NULL;
-        $workflow->system_roles = workflow_get_roles($permission);
-        // >> Now resume with normal flow.
-
-        // Make sure to escape the characters \ and '.
-        // The following method has the advantage, that you can export with
-        // features,
-        // and later import without enabling Features in the target system.
-        $workflow_export = addcslashes(entity_export($this->type, $workflow, '  '), '\\\'');
-        $workflow_identifier = features_var_export($identifier);
-        $code[] = "  // Exported workflow: {$workflow_identifier}";
-        $code[] = "  \$workflows[{$workflow_identifier}] = entity_import('{$this->type}', '" . $workflow_export . "');";
-        $code[] = "";
+        $this->export_render_workflow($workflow, $identifier, $code);
       }
     }
+
     $code[] = '  return $workflows;';
     $code = implode("\n", $code);
 
@@ -84,19 +68,155 @@ class WorkflowFeaturesController extends EntityDefaultFeaturesController {
   }
 
   /**
-   * Overridden to not delete upon revert.
+   * Renders the provided workflow into export code.
+   *
+   * @param Workflow $workflow
+   *   The workflow to export.
+   * @param string $identifier
+   *   The unique machine name for the workflow in the export.
+   * @param array $code
+   *   A reference to the export code array that will receive the output.
+   */
+  protected function export_render_workflow(Workflow $workflow, $identifier, array &$code) {
+    // Make sure data is not copied to the database.
+    $workflow = clone $workflow;
+
+    $this->sanitize_workflow_for_export($workflow);
+
+    // Make sure to escape the characters \ and '.
+    // The following method has the advantage, that you can export with
+    // features,
+    // and later import without enabling Features in the target system.
+    $workflow_export = addcslashes(entity_export($this->type, $workflow, '  '), '\\\'');
+    $workflow_identifier = features_var_export($identifier);
+
+    $code[] = "  // Exported workflow: {$workflow_identifier}";
+    $code[] = "  \$workflows[{$workflow_identifier}] = entity_import('{$this->type}', '" . $workflow_export . "');";
+    $code[] = ''; // Blank line
+  }
+
+  /**
+   * Prepares the provided workflow for export.
+   *
+   * Removes serial IDs and replaces them with machine names.
+   *
+   * @param Workflow $workflow
+   *   The workflow to sanitize. The contents of this object are modified directly.
+   */
+  protected function sanitize_workflow_for_export(Workflow $workflow) {
+    // Eliminate serial IDs in exports to prevent "Overridden" status.
+    // We use machine names instead.
+    unset($workflow->wid);
+
+    // Add roles to translate role IDs on target system.
+    $permission = NULL;
+
+    $workflow->system_roles = workflow_get_roles($permission);
+
+    $sid_to_name_map = $this->pack_states($workflow);
+    $this->pack_transitions($workflow, $sid_to_name_map);
+  }
+
+  /**
+   * "Packs" the states in the provided workflow into an export-friendly format.
+   *
+   * @param Workflow $workflow
+   *   The workflow to pack. The contents of this object are modified directly.
+   *
+   * @return array
+   *   A map of the old state IDs to their new machine names.
+   */
+  protected function pack_states(Workflow $workflow) {
+    $named_states    = array();
+    $sid_to_name_map = array();
+
+    foreach ($workflow->states as $state) {
+      /* @var WorkflowState $state */
+      $name = $state->getName();
+
+      $sid_to_name_map[$state->sid] = $name;
+
+      // Eliminate serial IDs in exports to prevent "Overridden" status.
+      // We use machine names instead.
+      unset($state->sid);
+      unset($state->wid);
+
+      $named_states[$name] = $state;
+    }
+
+    // Identify states by machine name.
+    $workflow->states = $named_states;
+
+    return $sid_to_name_map;
+  }
+
+  /**
+   * "Packs" the transitions in the provided workflow into an export-friendly format.
+   *
+   * @param Workflow $workflow
+   *   The workflow to pack. The contents of this object are modified directly.
+   *
+   * @param array $sid_to_name_map
+   *   The map of numeric state IDs to their machine names, for remapping sid
+   *   references.
+   */
+  protected function pack_transitions(Workflow $workflow, array $sid_to_name_map) {
+    $named_transitions = array();
+
+    foreach ($workflow->transitions as $transition) {
+      /* @var WorkflowTransition $transition */
+      $start_name = $sid_to_name_map[$transition->sid];
+      $end_name   = $sid_to_name_map[$transition->target_sid];
+      $new_name   = sprintf("%s_to_%s", $start_name, $end_name);
+
+      // Special case: replace parens in creation state transition names.
+      $new_name   = str_replace("(creation)", "_creation", $new_name);
+
+      $transition->name        = $new_name;
+      $transition->start_state = $start_name;
+      $transition->end_state   = $end_name;
+
+      // Eliminate serial IDs in exports to prevent "Overridden" status.
+      // We use machine names instead.
+      unset($transition->wid);
+      unset($transition->tid);
+      unset($transition->sid);
+      unset($transition->target_sid);
+
+      $named_transitions[$new_name] = $transition;
+    }
+
+    // Identify transitions by new machine name.
+    $workflow->transitions = $named_transitions;
+  }
+
+  /**
+   * Revert this workflow, either creating the workflow new (if one with the
+   * same machine name is not present), or updating the existing workflow.
+   *
+   * @param string $module
+   *   The name of the feature module whose components should be reverted.
    */
-/*
   function revert($module = NULL) {
     // Loads defaults from feature code.
-    $defaults = features_get_default($entity_type, $module);
-    if ($defaults = features_get_default($this->type, $module)) {
-      foreach ($defaults as $name => $entity) {
-        entity_delete($this->type, $name);
+    $defaults = features_get_default($this->type, $module);
+
+    foreach ($defaults as $machine_name => $entity) {
+      /* @var Workflow $entity */
+      $existing_workflow = workflow_load_by_name($machine_name);
+
+      if (!empty($existing_workflow)) {
+        /* @var Workflow $existing_workflow */
+
+        // Update existing entity.
+        $entity->wid         = $existing_workflow->wid;
+        $entity->is_new      = FALSE;
+        $entity->is_reverted = TRUE;
       }
+
+      $entity->save();
     }
   }
- */
 }
 
 /**
@@ -109,81 +229,33 @@ function workflow_features_pipe_field_base_alter(&$pipe, $data, $export) {
     foreach ($data as $field_name) {
       // $info = field_info_field($field_name);
       $field = _workflow_info_field($field_name);
-      if ($field['type'] == 'workflow') {
 
+      if ($field['type'] == 'workflow') {
         // $field['settings']['wid'] can be numeric or named.
         $workflow = workflow_load_single($field['settings']['wid']);
-        $pipe['Workflow'][] = $workflow->name;
+
+        // Fields might reference missing workflows.
+        if (!empty($workflow)) {
+          $pipe['Workflow'][] = $workflow->name;
+        }
       }
     }
   }
 }
 
 /**
- * Implements hook_features_rebuild().
- */
-function workflow_features_rebuild($module) {
-  // workflow_features_revert($module);
-  // $entity_type = 'Workflow';
-
-  // Do not delete the previous workflow. It will break the installation due to
-  // the new $wid that is created.
-  // return entity_features_get_controller($entity_type)->revert($module);
-
-  // $info = entity_get_info($entity_type);
-  // if (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) {
-  //   return entity_get_controller($entity_type)->import($export);
-  // }
-}
-
-/**
- * CRUD style helper functions below.
- */
-
-/**
- * Translates a role string to RIDs for importing.
+ * Implements hook_features_api_alter().
  *
- * @param string $role_string
- *   A string of roles or fake 'author' role.
- *
- * @return array
- *   An array of RIDs.
+ * Ensures Workflow always fires last during rebuild, to ensure that roles
+ * referenced by workflows to be loaded-in when features contain roles.
  */
-function _workflow_roles_to_rids($role_string) {
-  // Get all roles, including 'author'.
-  $permission = NULL;
-  $roles = workflow_get_roles($permission);
-
-  $rid_array = array();
-  foreach (explode(',', $role_string) as $role_name) {
-    if ($role_name === WORKFLOW_FEATURES_AUTHOR_NAME) {
-      $rid_array[WORKFLOW_ROLE_AUTHOR_RID] = WORKFLOW_ROLE_AUTHOR_RID;
-    }
-    elseif ($role_name && in_array($role_name, $roles)) {
-      $rid = array_search($role_name, $roles);
-      $rid_array[$rid] = $rid;
-    }
-  }
-  return $rid_array;
-}
+function workflow_features_api_alter(array &$components) {
+  // FIXME: Why is Workflow the only features provider with an uppercase component name?
+  $component_name = 'Workflow';
 
-/**
- * Translates a string of rids to role names for exporting.
- *
- * @param array $rid_array
- *   An array of rids or fake 'author' role.
- *
- * @return string
- *   A string of role names separated by commas.
- */
-function _workflow_rids_to_roles(array $rid_array) {
-  // Get all roles, including 'author'.
-  $permission = NULL;
-  $roles = workflow_get_roles($permission);
-  // There may be a role named 'author', so make 'author' distinct.
-  $roles[WORKFLOW_ROLE_AUTHOR_RID] = WORKFLOW_FEATURES_AUTHOR_NAME;
-
-  // Translate RIDs to rolenames.
-  $return = implode(',', array_intersect_key($roles, array_flip($rid_array)));
-  return trim($return, ',');
-}
+  if (isset($components[$component_name])) {
+    $setting = $components[$component_name];
+    unset($components[$component_name]);
+    $components[$component_name] = $setting;
+  }
+}
\ No newline at end of file
