diff --git a/include/crm/common/actions.h b/include/crm/common/actions.h
index 3109e307dd..c0390e868b 100644
--- a/include/crm/common/actions.h
+++ b/include/crm/common/actions.h
@@ -1,338 +1,341 @@
 /*
  * Copyright 2004-2023 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #ifndef PCMK__CRM_COMMON_ACTIONS__H
 #define PCMK__CRM_COMMON_ACTIONS__H
 
 #include <stdbool.h>                    // bool
 #include <strings.h>                    // strcasecmp()
 #include <glib.h>                       // gboolean, guint
 #include <libxml/tree.h>                // xmlNode
 
 #include <crm/lrmd_events.h>            // lrmd_event_data_t
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /*!
  * \file
  * \brief APIs related to actions
  * \ingroup core
  */
 
 //! Default timeout (in milliseconds) for non-metadata actions
 #define PCMK_DEFAULT_ACTION_TIMEOUT_MS      20000
 
 // @COMPAT We don't need a separate timeout for metadata, much less a longer one
 //! \deprecated Default timeout (in milliseconds) for metadata actions
 #define PCMK_DEFAULT_METADATA_TIMEOUT_MS    30000
 
 // Action names as strings
 #define PCMK_ACTION_CANCEL              "cancel"
 #define PCMK_ACTION_CLEAR_FAILCOUNT     "clear_failcount"
 #define PCMK_ACTION_CLONE_ONE_OR_MORE   "clone-one-or-more"
 #define PCMK_ACTION_DELETE              "delete"
 #define PCMK_ACTION_DEMOTE              "demote"
 #define PCMK_ACTION_DEMOTED             "demoted"
 #define PCMK_ACTION_DO_SHUTDOWN         "do_shutdown"
 #define PCMK_ACTION_LIST                "list"
 #define PCMK_ACTION_LRM_DELETE          "lrm_delete"
 #define PCMK_ACTION_LOAD_STOPPED        "load_stopped"
 #define PCMK_ACTION_MAINTENANCE_NODES   "maintenance_nodes"
 #define PCMK_ACTION_META_DATA           "meta-data"
 #define PCMK_ACTION_MIGRATE_FROM        "migrate_from"
 #define PCMK_ACTION_MIGRATE_TO          "migrate_to"
 #define PCMK_ACTION_MONITOR             "monitor"
 #define PCMK_ACTION_NOTIFIED            "notified"
 #define PCMK_ACTION_NOTIFY              "notify"
 #define PCMK_ACTION_OFF                 "off"
 #define PCMK_ACTION_ON                  "on"
 #define PCMK_ACTION_ONE_OR_MORE         "one-or-more"
 #define PCMK_ACTION_PROMOTE             "promote"
 #define PCMK_ACTION_PROMOTED            "promoted"
 #define PCMK_ACTION_REBOOT              "reboot"
 #define PCMK_ACTION_RELOAD              "reload"
 #define PCMK_ACTION_RELOAD_AGENT        "reload-agent"
 #define PCMK_ACTION_RUNNING             "running"
 #define PCMK_ACTION_START               "start"
 #define PCMK_ACTION_STATUS              "status"
 #define PCMK_ACTION_STONITH             "stonith"
 #define PCMK_ACTION_STOP                "stop"
 #define PCMK_ACTION_STOPPED             "stopped"
 #define PCMK_ACTION_VALIDATE_ALL        "validate-all"
 
 //! Possible actions (including some pseudo-actions)
 enum action_tasks {
     pcmk_action_unspecified = 0,    //!< Unspecified or unknown action
     pcmk_action_monitor,            //!< Monitor
 
     // Each "completed" action must be the regular action plus 1
 
     pcmk_action_stop,               //!< Stop
     pcmk_action_stopped,            //!< Stop completed
 
     pcmk_action_start,              //!< Start
     pcmk_action_started,            //!< Start completed
 
     pcmk_action_notify,             //!< Notify
     pcmk_action_notified,           //!< Notify completed
 
     pcmk_action_promote,            //!< Promote
     pcmk_action_promoted,           //!< Promoted
 
     pcmk_action_demote,             //!< Demote
     pcmk_action_demoted,            //!< Demoted
 
     pcmk_action_shutdown,           //!< Shut down node
     pcmk_action_fence,              //!< Fence node
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
     //! \deprecated Use pcmk_action_unspecified instead
     no_action               = pcmk_action_unspecified,
 
     //! \deprecated Use pcmk_action_monitor instead
     monitor_rsc             = pcmk_action_monitor,
 
     //! \deprecated Use pcmk_action_stop instead
     stop_rsc                = pcmk_action_stop,
 
     //! \deprecated Use pcmk_action_stopped instead
     stopped_rsc             = pcmk_action_stopped,
 
     //! \deprecated Use pcmk_action_start instead
     start_rsc               = pcmk_action_start,
 
     //! \deprecated Use pcmk_action_started instead
     started_rsc             = pcmk_action_started,
 
     //! \deprecated Use pcmk_action_notify instead
     action_notify           = pcmk_action_notify,
 
     //! \deprecated Use pcmk_action_notified instead
     action_notified         = pcmk_action_notified,
 
     //! \deprecated Use pcmk_action_promote instead
     action_promote          = pcmk_action_promote,
 
     //! \deprecated Use pcmk_action_promoted instead
     action_promoted         = pcmk_action_promoted,
 
     //! \deprecated Use pcmk_action_demote instead
     action_demote           = pcmk_action_demote,
 
     //! \deprecated Use pcmk_action_demoted instead
     action_demoted          = pcmk_action_demoted,
 
     //! \deprecated Use pcmk_action_shutdown instead
     shutdown_crm            = pcmk_action_shutdown,
 
     //! \deprecated Use pcmk_action_fence instead
     stonith_node            = pcmk_action_fence,
 #endif
 };
 
 //! Possible responses to a resource action failure
 enum action_fail_response {
     /* The order is (partially) significant here; the values from
      * pcmk_on_fail_ignore through pcmk_on_fail_fence_node are in order of
      * increasing severity.
      *
      * @COMPAT The values should be ordered and numbered per the "TODO" comments
      *         below, so all values are in order of severity and there is room for
      *         future additions, but that would break API compatibility.
      * @TODO   For now, we just use a function to compare the values specially, but
      *         at the next compatibility break, we should arrange things
      *         properly so we can compare with less than and greater than.
      */
 
     // @TODO Define as 10
     pcmk_on_fail_ignore             = 0,    //!< Act as if failure didn't happen
 
     // @TODO Define as 30
     pcmk_on_fail_restart            = 1,    //!< Restart resource
 
     // @TODO Define as 60
     pcmk_on_fail_ban                = 2,    //!< Ban resource from current node
 
     // @TODO Define as 70
     pcmk_on_fail_block              = 3,    //!< Treat resource as unmanaged
 
     // @TODO Define as 80
     pcmk_on_fail_stop               = 4,    //!< Stop resource and leave stopped
 
     // @TODO Define as 90
     pcmk_on_fail_standby_node       = 5,    //!< Put resource's node in standby
 
     // @TODO Define as 100
     pcmk_on_fail_fence_node         = 6,    //!< Fence resource's node
 
     // @COMPAT Values below here are out of desired order for API compatibility
 
     // @TODO Define as 50
     pcmk_on_fail_restart_container  = 7,    //!< Restart resource's container
 
     // @TODO Define as 40
     /*!
      * Fence the remote node created by the resource if fencing is enabled,
      * otherwise attempt to restart the resource (used internally for some
      * remote connection failures).
      */
     pcmk_on_fail_reset_remote       = 8,
 
     // @TODO Define as 20
     pcmk_on_fail_demote             = 9,    //!< Demote if promotable, else stop
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
     //! \deprecated Use pcmk_on_fail_ignore instead
     action_fail_ignore              = pcmk_on_fail_ignore,
 
     //! \deprecated Use pcmk_on_fail_restart instead
     action_fail_recover             = pcmk_on_fail_restart,
 
     //! \deprecated Use pcmk_on_fail_ban instead
     action_fail_migrate             = pcmk_on_fail_ban,
 
     //! \deprecated Use pcmk_on_fail_block instead
     action_fail_block               = pcmk_on_fail_block,
 
     //! \deprecated Use pcmk_on_fail_stop instead
     action_fail_stop                = pcmk_on_fail_stop,
 
     //! \deprecated Use pcmk_on_fail_standby_node instead
     action_fail_standby             = pcmk_on_fail_standby_node,
 
     //! \deprecated Use pcmk_on_fail_fence_node instead
     action_fail_fence               = pcmk_on_fail_fence_node,
 
     //! \deprecated Use pcmk_on_fail_restart_container instead
     action_fail_restart_container   = pcmk_on_fail_restart_container,
 
     //! \deprecated Use pcmk_on_fail_reset_remote instead
     action_fail_reset_remote        = pcmk_on_fail_reset_remote,
 
     //! \deprecated Use pcmk_on_fail_demote instead
     action_fail_demote              = pcmk_on_fail_demote,
 #endif
 };
 
 //! Action scheduling flags
 enum pe_action_flags {
     //! No action flags set (compare with equality rather than bit set)
     pcmk_no_action_flags            = 0,
 
     //! Whether action does not require invoking an agent
     pcmk_action_pseudo              = (1 << 0),
 
     //! Whether action is runnable
     pcmk_action_runnable            = (1 << 1),
 
     //! Whether action should not be executed
     pcmk_action_optional            = (1 << 2),
 
     //! Whether action should be added to transition graph even if optional
     pcmk_action_always_in_graph     = (1 << 3),
 
     //! Whether operation-specific instance attributes have been unpacked yet
     pcmk_action_attrs_evaluated     = (1 << 4),
 
     //! Whether action can be related to a live migration
     pcmk_action_migratable           = (1 << 7),
 
     //! Whether action has been added to transition graph
     pcmk_action_added_to_graph       = (1 << 8),
 
     //! Whether action is a stop to abort a dangling migration
     pcmk_action_migration_abort      = (1 << 11),
 
     /*!
      * Whether action is an ordering point for minimum required instances
      * (used to implement ordering after clones with clone-min configured,
      * and ordered sets with require-all=false)
      */
     pcmk_action_min_runnable         = (1 << 12),
 
     //! Whether action is recurring monitor that must be rescheduled if active
     pcmk_action_reschedule           = (1 << 13),
 
+    //! Whether action has already been processed by a recursive procedure
+    pcmk_action_detect_loop          = (1 << 14),
+
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
     //! \deprecated Use pcmk_action_pseudo instead
     pe_action_pseudo                = pcmk_action_pseudo,
 
     //! \deprecated Use pcmk_action_runnable instead
     pe_action_runnable              = pcmk_action_runnable,
 
     //! \deprecated Use pcmk_action_optional instead
     pe_action_optional              = pcmk_action_optional,
 
     //! \deprecated Use pcmk_action_always_in_graph instead
     pe_action_print_always          = pcmk_action_always_in_graph,
 
     //! \deprecated Use pcmk_action_attrs_evaluated instead
     pe_action_have_node_attrs       = pcmk_action_attrs_evaluated,
 
     //! \deprecated Do not use
     pe_action_implied_by_stonith    = (1 << 6),
 
     //! \deprecated Use pcmk_action_migratable instead
     pe_action_migrate_runnable      = pcmk_action_migratable,
 
     //! \deprecated Use pcmk_action_added_to_graph instead
     pe_action_dumped                = pcmk_action_added_to_graph,
 
     //! \deprecated Do not use
     pe_action_processed             = (1 << 9),
 
     //! \deprecated Do not use
     pe_action_clear                 = (1 << 10),
 
     //! \deprecated Use pcmk_action_migration_abort instead
     pe_action_dangle                = pcmk_action_migration_abort,
 
     //! \deprecated Use pcmk_action_min_runnable instead
     pe_action_requires_any          = pcmk_action_min_runnable,
 
     //! \deprecated Use pcmk_action_reschedule instead
     pe_action_reschedule            = pcmk_action_reschedule,
 #endif
 
-    pe_action_tracking = 0x04000,
+    pe_action_tracking              = pcmk_action_detect_loop,
     pe_action_dedup = 0x08000, //! Internal state tracking when creating graph
 
     pe_action_dc = 0x10000,         //! Action may run on DC instead of target
 };
 
 // For parsing various action-related string specifications
 gboolean parse_op_key(const char *key, char **rsc_id, char **op_type,
                       guint *interval_ms);
 gboolean decode_transition_key(const char *key, char **uuid, int *transition_id,
                                int *action_id, int *target_rc);
 gboolean decode_transition_magic(const char *magic, char **uuid,
                                  int *transition_id, int *action_id,
                                  int *op_status, int *op_rc, int *target_rc);
 
 // @COMPAT Either these shouldn't be in libcrmcommon or lrmd_event_data_t should
 int rsc_op_expected_rc(const lrmd_event_data_t *event);
 gboolean did_rsc_op_fail(lrmd_event_data_t *event, int target_rc);
 
 bool crm_op_needs_metadata(const char *rsc_class, const char *op);
 
 xmlNode *crm_create_op_xml(xmlNode *parent, const char *prefix,
                            const char *task, const char *interval_spec,
                            const char *timeout);
 
 bool pcmk_is_probe(const char *task, guint interval);
 bool pcmk_xe_is_probe(const xmlNode *xml_op);
 bool pcmk_xe_mask_probe_failure(const xmlNode *xml_op);
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif // PCMK__CRM_COMMON_ACTIONS__H
diff --git a/lib/pacemaker/pcmk_graph_producer.c b/lib/pacemaker/pcmk_graph_producer.c
index 69e1d4e0fc..c1465207ec 100644
--- a/lib/pacemaker/pcmk_graph_producer.c
+++ b/lib/pacemaker/pcmk_graph_producer.c
@@ -1,1095 +1,1095 @@
 /*
  * Copyright 2004-2023 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 #include <crm/crm.h>
 #include <crm/cib.h>
 #include <crm/msg_xml.h>
 #include <crm/common/xml.h>
 
 #include <glib.h>
 
 #include <pacemaker-internal.h>
 
 #include "libpacemaker_private.h"
 
 // Convenience macros for logging action properties
 
 #define action_type_str(flags) \
     (pcmk_is_set((flags), pcmk_action_pseudo)? "pseudo-action" : "action")
 
 #define action_optional_str(flags) \
     (pcmk_is_set((flags), pcmk_action_optional)? "optional" : "required")
 
 #define action_runnable_str(flags) \
     (pcmk_is_set((flags), pcmk_action_runnable)? "runnable" : "unrunnable")
 
 #define action_node_str(a) \
     (((a)->node == NULL)? "no node" : (a)->node->details->uname)
 
 /*!
  * \internal
  * \brief Add an XML node tag for a specified ID
  *
  * \param[in]     id      Node UUID to add
  * \param[in,out] xml     Parent XML tag to add to
  */
 static xmlNode*
 add_node_to_xml_by_id(const char *id, xmlNode *xml)
 {
     xmlNode *node_xml;
 
     node_xml = create_xml_node(xml, XML_CIB_TAG_NODE);
     crm_xml_add(node_xml, XML_ATTR_ID, id);
 
     return node_xml;
 }
 
 /*!
  * \internal
  * \brief Add an XML node tag for a specified node
  *
  * \param[in]     node  Node to add
  * \param[in,out] xml   XML to add node to
  */
 static void
 add_node_to_xml(const pe_node_t *node, void *xml)
 {
     add_node_to_xml_by_id(node->details->id, (xmlNode *) xml);
 }
 
 /*!
  * \internal
  * \brief Count (optionally add to XML) nodes needing maintenance state update
  *
  * \param[in,out] xml       Parent XML tag to add to, if any
  * \param[in]     data_set  Working set for cluster
  *
  * \return Count of nodes added
  * \note Only Pacemaker Remote nodes are considered currently
  */
 static int
 add_maintenance_nodes(xmlNode *xml, const pe_working_set_t *data_set)
 {
     xmlNode *maintenance = NULL;
     int count = 0;
 
     if (xml != NULL) {
         maintenance = create_xml_node(xml, XML_GRAPH_TAG_MAINTENANCE);
     }
     for (const GList *iter = data_set->nodes; iter != NULL; iter = iter->next) {
         const pe_node_t *node = iter->data;
 
         if (pe__is_guest_or_remote_node(node) &&
             (node->details->maintenance != node->details->remote_maintenance)) {
 
             if (maintenance != NULL) {
                 crm_xml_add(add_node_to_xml_by_id(node->details->id,
                                                   maintenance),
                             XML_NODE_IS_MAINTENANCE,
                             (node->details->maintenance? "1" : "0"));
             }
             count++;
         }
     }
     crm_trace("%s %d nodes in need of maintenance mode update in state",
               ((maintenance == NULL)? "Counted" : "Added"), count);
     return count;
 }
 
 /*!
  * \internal
  * \brief Add pseudo action with nodes needing maintenance state update
  *
  * \param[in,out] data_set  Working set for cluster
  */
 static void
 add_maintenance_update(pe_working_set_t *data_set)
 {
     pe_action_t *action = NULL;
 
     if (add_maintenance_nodes(NULL, data_set) != 0) {
         action = get_pseudo_op(PCMK_ACTION_MAINTENANCE_NODES, data_set);
         pe__set_action_flags(action, pcmk_action_always_in_graph);
     }
 }
 
 /*!
  * \internal
  * \brief Add XML with nodes that an action is expected to bring down
  *
  * If a specified action is expected to bring any nodes down, add an XML block
  * with their UUIDs. When a node is lost, this allows the controller to
  * determine whether it was expected.
  *
  * \param[in,out] xml       Parent XML tag to add to
  * \param[in]     action    Action to check for downed nodes
  */
 static void
 add_downed_nodes(xmlNode *xml, const pe_action_t *action)
 {
     CRM_CHECK((xml != NULL) && (action != NULL) && (action->node != NULL),
               return);
 
     if (pcmk__str_eq(action->task, PCMK_ACTION_DO_SHUTDOWN, pcmk__str_none)) {
 
         /* Shutdown makes the action's node down */
         xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED);
         add_node_to_xml_by_id(action->node->details->id, downed);
 
     } else if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH,
                             pcmk__str_none)) {
 
         /* Fencing makes the action's node and any hosted guest nodes down */
         const char *fence = g_hash_table_lookup(action->meta, "stonith_action");
 
         if (pcmk__is_fencing_action(fence)) {
             xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED);
             add_node_to_xml_by_id(action->node->details->id, downed);
             pe_foreach_guest_node(action->node->details->data_set, action->node,
                                   add_node_to_xml, downed);
         }
 
     } else if (action->rsc && action->rsc->is_remote_node
                && pcmk__str_eq(action->task, PCMK_ACTION_STOP,
                                pcmk__str_none)) {
 
         /* Stopping a remote connection resource makes connected node down,
          * unless it's part of a migration
          */
         GList *iter;
         pe_action_t *input;
         bool migrating = false;
 
         for (iter = action->actions_before; iter != NULL; iter = iter->next) {
             input = ((pe_action_wrapper_t *) iter->data)->action;
             if ((input->rsc != NULL)
                 && pcmk__str_eq(action->rsc->id, input->rsc->id, pcmk__str_none)
                 && pcmk__str_eq(input->task, PCMK_ACTION_MIGRATE_FROM,
                                 pcmk__str_none)) {
                 migrating = true;
                 break;
             }
         }
         if (!migrating) {
             xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED);
             add_node_to_xml_by_id(action->rsc->id, downed);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Create a transition graph operation key for a clone action
  *
  * \param[in] action       Clone action
  * \param[in] interval_ms  Action interval in milliseconds
  *
  * \return Newly allocated string with transition graph operation key
  */
 static char *
 clone_op_key(const pe_action_t *action, guint interval_ms)
 {
     if (pcmk__str_eq(action->task, PCMK_ACTION_NOTIFY, pcmk__str_none)) {
         const char *n_type = g_hash_table_lookup(action->meta, "notify_type");
         const char *n_task = g_hash_table_lookup(action->meta,
                                                  "notify_operation");
 
         CRM_LOG_ASSERT((n_type != NULL) && (n_task != NULL));
         return pcmk__notify_key(action->rsc->clone_name, n_type, n_task);
 
     } else if (action->cancel_task != NULL) {
         return pcmk__op_key(action->rsc->clone_name, action->cancel_task,
                             interval_ms);
     } else {
         return pcmk__op_key(action->rsc->clone_name, action->task, interval_ms);
     }
 }
 
 /*!
  * \internal
  * \brief Add node details to transition graph action XML
  *
  * \param[in]     action  Scheduled action
  * \param[in,out] xml     Transition graph action XML for \p action
  */
 static void
 add_node_details(const pe_action_t *action, xmlNode *xml)
 {
     pe_node_t *router_node = pcmk__connection_host_for_action(action);
 
     crm_xml_add(xml, XML_LRM_ATTR_TARGET, action->node->details->uname);
     crm_xml_add(xml, XML_LRM_ATTR_TARGET_UUID, action->node->details->id);
     if (router_node != NULL) {
         crm_xml_add(xml, XML_LRM_ATTR_ROUTER_NODE, router_node->details->uname);
     }
 }
 
 /*!
  * \internal
  * \brief Add resource details to transition graph action XML
  *
  * \param[in]     action      Scheduled action
  * \param[in,out] action_xml  Transition graph action XML for \p action
  */
 static void
 add_resource_details(const pe_action_t *action, xmlNode *action_xml)
 {
     xmlNode *rsc_xml = NULL;
     const char *attr_list[] = {
         XML_AGENT_ATTR_CLASS,
         XML_AGENT_ATTR_PROVIDER,
         XML_ATTR_TYPE
     };
 
     /* If a resource is locked to a node via shutdown-lock, mark its actions
      * so the controller can preserve the lock when the action completes.
      */
     if (pcmk__action_locks_rsc_to_node(action)) {
         crm_xml_add_ll(action_xml, XML_CONFIG_ATTR_SHUTDOWN_LOCK,
                        (long long) action->rsc->lock_time);
     }
 
     // List affected resource
 
     rsc_xml = create_xml_node(action_xml,
                               (const char *) action->rsc->xml->name);
     if (pcmk_is_set(action->rsc->flags, pcmk_rsc_removed)
         && (action->rsc->clone_name != NULL)) {
         /* Use the numbered instance name here, because if there is more
          * than one instance on a node, we need to make sure the command
          * goes to the right one.
          *
          * This is important even for anonymous clones, because the clone's
          * unique meta-attribute might have just been toggled from on to
          * off.
          */
         crm_debug("Using orphan clone name %s instead of %s",
                   action->rsc->id, action->rsc->clone_name);
         crm_xml_add(rsc_xml, XML_ATTR_ID, action->rsc->clone_name);
         crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->id);
 
     } else if (!pcmk_is_set(action->rsc->flags, pcmk_rsc_unique)) {
         const char *xml_id = ID(action->rsc->xml);
 
         crm_debug("Using anonymous clone name %s for %s (aka %s)",
                   xml_id, action->rsc->id, action->rsc->clone_name);
 
         /* ID is what we'd like client to use
          * ID_LONG is what they might know it as instead
          *
          * ID_LONG is only strictly needed /here/ during the
          * transition period until all nodes in the cluster
          * are running the new software /and/ have rebooted
          * once (meaning that they've only ever spoken to a DC
          * supporting this feature).
          *
          * If anyone toggles the unique flag to 'on', the
          * 'instance free' name will correspond to an orphan
          * and fall into the clause above instead
          */
         crm_xml_add(rsc_xml, XML_ATTR_ID, xml_id);
         if ((action->rsc->clone_name != NULL)
             && !pcmk__str_eq(xml_id, action->rsc->clone_name,
                              pcmk__str_none)) {
             crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->clone_name);
         } else {
             crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->id);
         }
 
     } else {
         CRM_ASSERT(action->rsc->clone_name == NULL);
         crm_xml_add(rsc_xml, XML_ATTR_ID, action->rsc->id);
     }
 
     for (int lpc = 0; lpc < PCMK__NELEM(attr_list); lpc++) {
         crm_xml_add(rsc_xml, attr_list[lpc],
                     g_hash_table_lookup(action->rsc->meta, attr_list[lpc]));
     }
 }
 
 /*!
  * \internal
  * \brief Add action attributes to transition graph action XML
  *
  * \param[in,out] action      Scheduled action
  * \param[in,out] action_xml  Transition graph action XML for \p action
  */
 static void
 add_action_attributes(pe_action_t *action, xmlNode *action_xml)
 {
     xmlNode *args_xml = NULL;
 
     /* We create free-standing XML to start, so we can sort the attributes
      * before adding it to action_xml, which keeps the scheduler regression
      * test graphs comparable.
      */
     args_xml = create_xml_node(NULL, XML_TAG_ATTRS);
 
     crm_xml_add(args_xml, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
     g_hash_table_foreach(action->extra, hash2field, args_xml);
 
     if ((action->rsc != NULL) && (action->node != NULL)) {
         // Get the resource instance attributes, evaluated properly for node
         GHashTable *params = pe_rsc_params(action->rsc, action->node,
                                            action->rsc->cluster);
 
         pcmk__substitute_remote_addr(action->rsc, params);
 
         g_hash_table_foreach(params, hash2smartfield, args_xml);
 
     } else if ((action->rsc != NULL)
                && (action->rsc->variant <= pcmk_rsc_variant_primitive)) {
         GHashTable *params = pe_rsc_params(action->rsc, NULL,
                                            action->rsc->cluster);
 
         g_hash_table_foreach(params, hash2smartfield, args_xml);
     }
 
     g_hash_table_foreach(action->meta, hash2metafield, args_xml);
     if (action->rsc != NULL) {
         pe_resource_t *parent = action->rsc;
 
         while (parent != NULL) {
             parent->cmds->add_graph_meta(parent, args_xml);
             parent = parent->parent;
         }
 
         pcmk__add_bundle_meta_to_xml(args_xml, action);
 
     } else if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH, pcmk__str_none)
                && (action->node != NULL)) {
         /* Pass the node's attributes as meta-attributes.
          *
          * @TODO: Determine whether it is still necessary to do this. It was
          * added in 33d99707, probably for the libfence-based implementation in
          * c9a90bd, which is no longer used.
          */
         g_hash_table_foreach(action->node->details->attrs, hash2metafield,
                              args_xml);
     }
 
     sorted_xml(args_xml, action_xml, FALSE);
     free_xml(args_xml);
 }
 
 /*!
  * \internal
  * \brief Create the transition graph XML for a scheduled action
  *
  * \param[in,out] parent        Parent XML element to add action to
  * \param[in,out] action        Scheduled action
  * \param[in]     skip_details  If false, add action details as sub-elements
  * \param[in]     data_set      Cluster working set
  */
 static void
 create_graph_action(xmlNode *parent, pe_action_t *action, bool skip_details,
                     const pe_working_set_t *data_set)
 {
     bool needs_node_info = true;
     bool needs_maintenance_info = false;
     xmlNode *action_xml = NULL;
 
     if ((action == NULL) || (data_set == NULL)) {
         return;
     }
 
     // Create the top-level element based on task
 
     if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH, pcmk__str_none)) {
         /* All fences need node info; guest node fences are pseudo-events */
         if (pcmk_is_set(action->flags, pcmk_action_pseudo)) {
             action_xml = create_xml_node(parent, XML_GRAPH_TAG_PSEUDO_EVENT);
         } else {
             action_xml = create_xml_node(parent, XML_GRAPH_TAG_CRM_EVENT);
         }
 
     } else if (pcmk__str_any_of(action->task,
                                 PCMK_ACTION_DO_SHUTDOWN,
                                 PCMK_ACTION_CLEAR_FAILCOUNT, NULL)) {
         action_xml = create_xml_node(parent, XML_GRAPH_TAG_CRM_EVENT);
 
     } else if (pcmk__str_eq(action->task, PCMK_ACTION_LRM_DELETE,
                             pcmk__str_none)) {
         // CIB-only clean-up for shutdown locks
         action_xml = create_xml_node(parent, XML_GRAPH_TAG_CRM_EVENT);
         crm_xml_add(action_xml, PCMK__XA_MODE, XML_TAG_CIB);
 
     } else if (pcmk_is_set(action->flags, pcmk_action_pseudo)) {
         if (pcmk__str_eq(action->task, PCMK_ACTION_MAINTENANCE_NODES,
                          pcmk__str_none)) {
             needs_maintenance_info = true;
         }
         action_xml = create_xml_node(parent, XML_GRAPH_TAG_PSEUDO_EVENT);
         needs_node_info = false;
 
     } else {
         action_xml = create_xml_node(parent, XML_GRAPH_TAG_RSC_OP);
     }
 
     crm_xml_add_int(action_xml, XML_ATTR_ID, action->id);
     crm_xml_add(action_xml, XML_LRM_ATTR_TASK, action->task);
 
     if ((action->rsc != NULL) && (action->rsc->clone_name != NULL)) {
         char *clone_key = NULL;
         guint interval_ms;
 
         if (pcmk__guint_from_hash(action->meta, XML_LRM_ATTR_INTERVAL_MS, 0,
                                   &interval_ms) != pcmk_rc_ok) {
             interval_ms = 0;
         }
         clone_key = clone_op_key(action, interval_ms);
         crm_xml_add(action_xml, XML_LRM_ATTR_TASK_KEY, clone_key);
         crm_xml_add(action_xml, "internal_" XML_LRM_ATTR_TASK_KEY,
                     action->uuid);
         free(clone_key);
     } else {
         crm_xml_add(action_xml, XML_LRM_ATTR_TASK_KEY, action->uuid);
     }
 
     if (needs_node_info && (action->node != NULL)) {
         add_node_details(action, action_xml);
         g_hash_table_insert(action->meta, strdup(XML_LRM_ATTR_TARGET),
                             strdup(action->node->details->uname));
         g_hash_table_insert(action->meta, strdup(XML_LRM_ATTR_TARGET_UUID),
                             strdup(action->node->details->id));
     }
 
     if (skip_details) {
         return;
     }
 
     if ((action->rsc != NULL)
         && !pcmk_is_set(action->flags, pcmk_action_pseudo)) {
 
         // This is a real resource action, so add resource details
         add_resource_details(action, action_xml);
     }
 
     /* List any attributes in effect */
     add_action_attributes(action, action_xml);
 
     /* List any nodes this action is expected to make down */
     if (needs_node_info && (action->node != NULL)) {
         add_downed_nodes(action_xml, action);
     }
 
     if (needs_maintenance_info) {
         add_maintenance_nodes(action_xml, data_set);
     }
 }
 
 /*!
  * \internal
  * \brief Check whether an action should be added to the transition graph
  *
  * \param[in] action  Action to check
  *
  * \return true if action should be added to graph, otherwise false
  */
 static bool
 should_add_action_to_graph(const pe_action_t *action)
 {
     if (!pcmk_is_set(action->flags, pcmk_action_runnable)) {
         crm_trace("Ignoring action %s (%d): unrunnable",
                   action->uuid, action->id);
         return false;
     }
 
     if (pcmk_is_set(action->flags, pcmk_action_optional)
         && !pcmk_is_set(action->flags, pcmk_action_always_in_graph)) {
         crm_trace("Ignoring action %s (%d): optional",
                   action->uuid, action->id);
         return false;
     }
 
     /* Actions for unmanaged resources should be excluded from the graph,
      * with the exception of monitors and cancellation of recurring monitors.
      */
     if ((action->rsc != NULL)
         && !pcmk_is_set(action->rsc->flags, pcmk_rsc_managed)
         && !pcmk__str_eq(action->task, PCMK_ACTION_MONITOR, pcmk__str_none)) {
 
         const char *interval_ms_s;
 
         /* A cancellation of a recurring monitor will get here because the task
          * is cancel rather than monitor, but the interval can still be used to
          * recognize it. The interval has been normalized to milliseconds by
          * this point, so a string comparison is sufficient.
          */
         interval_ms_s = g_hash_table_lookup(action->meta,
                                             XML_LRM_ATTR_INTERVAL_MS);
         if (pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches)) {
             crm_trace("Ignoring action %s (%d): for unmanaged resource (%s)",
                       action->uuid, action->id, action->rsc->id);
             return false;
         }
     }
 
     /* Always add pseudo-actions, fence actions, and shutdown actions (already
      * determined to be required and runnable by this point)
      */
     if (pcmk_is_set(action->flags, pcmk_action_pseudo)
         || pcmk__strcase_any_of(action->task, PCMK_ACTION_STONITH,
                                 PCMK_ACTION_DO_SHUTDOWN, NULL)) {
         return true;
     }
 
     if (action->node == NULL) {
         pe_err("Skipping action %s (%d) "
                "because it was not assigned to a node (bug?)",
                action->uuid, action->id);
         pcmk__log_action("Unassigned", action, false);
         return false;
     }
 
     if (pcmk_is_set(action->flags, pe_action_dc)) {
         crm_trace("Action %s (%d) should be dumped: "
                   "can run on DC instead of %s",
                   action->uuid, action->id, pe__node_name(action->node));
 
     } else if (pe__is_guest_node(action->node)
                && !action->node->details->remote_requires_reset) {
         crm_trace("Action %s (%d) should be dumped: "
                   "assuming will be runnable on guest %s",
                   action->uuid, action->id, pe__node_name(action->node));
 
     } else if (!action->node->details->online) {
         pe_err("Skipping action %s (%d) "
                "because it was scheduled for offline node (bug?)",
                action->uuid, action->id);
         pcmk__log_action("Offline node", action, false);
         return false;
 
     } else if (action->node->details->unclean) {
         pe_err("Skipping action %s (%d) "
                "because it was scheduled for unclean node (bug?)",
                action->uuid, action->id);
         pcmk__log_action("Unclean node", action, false);
         return false;
     }
     return true;
 }
 
 /*!
  * \internal
  * \brief Check whether an ordering's flags can change an action
  *
  * \param[in] ordering  Ordering to check
  *
  * \return true if ordering has flags that can change an action, false otherwise
  */
 static bool
 ordering_can_change_actions(const pe_action_wrapper_t *ordering)
 {
     return pcmk_any_flags_set(ordering->type, ~(pe_order_implies_first_printed
                                                 |pe_order_implies_then_printed
                                                 |pe_order_optional));
 }
 
 /*!
  * \internal
  * \brief Check whether an action input should be in the transition graph
  *
  * \param[in]     action  Action to check
  * \param[in,out] input   Action input to check
  *
  * \return true if input should be in graph, false otherwise
  * \note This function may not only check an input, but disable it under certian
  *       circumstances (load or anti-colocation orderings that are not needed).
  */
 static bool
 should_add_input_to_graph(const pe_action_t *action, pe_action_wrapper_t *input)
 {
     if (input->state == pe_link_dumped) {
         return true;
     }
 
     if (input->type == pe_order_none) {
         crm_trace("Ignoring %s (%d) input %s (%d): "
                   "ordering disabled",
                   action->uuid, action->id,
                   input->action->uuid, input->action->id);
         return false;
 
     } else if (!pcmk_is_set(input->action->flags, pcmk_action_runnable)
                && !ordering_can_change_actions(input)) {
         crm_trace("Ignoring %s (%d) input %s (%d): "
                   "optional and input unrunnable",
                   action->uuid, action->id,
                   input->action->uuid, input->action->id);
         return false;
 
     } else if (!pcmk_is_set(input->action->flags, pcmk_action_runnable)
                && pcmk_is_set(input->type, pe_order_one_or_more)) {
         crm_trace("Ignoring %s (%d) input %s (%d): "
                   "one-or-more and input unrunnable",
                   action->uuid, action->id,
                   input->action->uuid, input->action->id);
         return false;
 
     } else if (pcmk_is_set(input->type, pe_order_implies_first_migratable)
                && !pcmk_is_set(input->action->flags, pcmk_action_runnable)) {
         crm_trace("Ignoring %s (%d) input %s (%d): "
                   "implies input migratable but input unrunnable",
                   action->uuid, action->id,
                   input->action->uuid, input->action->id);
         return false;
 
     } else if (pcmk_is_set(input->type, pe_order_apply_first_non_migratable)
                && pcmk_is_set(input->action->flags, pcmk_action_migratable)) {
         crm_trace("Ignoring %s (%d) input %s (%d): "
                   "only if input unmigratable but input unrunnable",
                   action->uuid, action->id,
                   input->action->uuid, input->action->id);
         return false;
 
     } else if ((input->type == pe_order_optional)
                && pcmk_is_set(input->action->flags, pcmk_action_migratable)
                && pcmk__ends_with(input->action->uuid, "_stop_0")) {
         crm_trace("Ignoring %s (%d) input %s (%d): "
                   "optional but stop in migration",
                   action->uuid, action->id,
                   input->action->uuid, input->action->id);
         return false;
 
     } else if (input->type == pe_order_load) {
         pe_node_t *input_node = input->action->node;
 
         // load orderings are relevant only if actions are for same node
 
         if ((action->rsc != NULL)
             && pcmk__str_eq(action->task, PCMK_ACTION_MIGRATE_TO,
                             pcmk__str_none)) {
 
             pe_node_t *assigned = action->rsc->allocated_to;
 
             /* For load_stopped -> migrate_to orderings, we care about where it
              * has been assigned to, not where it will be executed.
              */
             if (!pe__same_node(input_node, assigned)) {
                 crm_trace("Ignoring %s (%d) input %s (%d): "
                           "load ordering node mismatch %s vs %s",
                           action->uuid, action->id,
                           input->action->uuid, input->action->id,
                           (assigned? assigned->details->uname : "<none>"),
                           (input_node? input_node->details->uname : "<none>"));
                 input->type = pe_order_none;
                 return false;
             }
 
         } else if (!pe__same_node(input_node, action->node)) {
             crm_trace("Ignoring %s (%d) input %s (%d): "
                       "load ordering node mismatch %s vs %s",
                       action->uuid, action->id,
                       input->action->uuid, input->action->id,
                       (action->node? action->node->details->uname : "<none>"),
                       (input_node? input_node->details->uname : "<none>"));
             input->type = pe_order_none;
             return false;
 
         } else if (pcmk_is_set(input->action->flags, pcmk_action_optional)) {
             crm_trace("Ignoring %s (%d) input %s (%d): "
                       "load ordering input optional",
                       action->uuid, action->id,
                       input->action->uuid, input->action->id);
             input->type = pe_order_none;
             return false;
         }
 
     } else if (input->type == pe_order_anti_colocation) {
         if (input->action->node && action->node
             && !pe__same_node(input->action->node, action->node)) {
             crm_trace("Ignoring %s (%d) input %s (%d): "
                       "anti-colocation node mismatch %s vs %s",
                       action->uuid, action->id,
                       input->action->uuid, input->action->id,
                       pe__node_name(action->node),
                       pe__node_name(input->action->node));
             input->type = pe_order_none;
             return false;
 
         } else if (pcmk_is_set(input->action->flags, pcmk_action_optional)) {
             crm_trace("Ignoring %s (%d) input %s (%d): "
                       "anti-colocation input optional",
                       action->uuid, action->id,
                       input->action->uuid, input->action->id);
             input->type = pe_order_none;
             return false;
         }
 
     } else if (input->action->rsc
                && input->action->rsc != action->rsc
                && pcmk_is_set(input->action->rsc->flags, pcmk_rsc_failed)
                && !pcmk_is_set(input->action->rsc->flags, pcmk_rsc_managed)
                && pcmk__ends_with(input->action->uuid, "_stop_0")
                && action->rsc && pe_rsc_is_clone(action->rsc)) {
         crm_warn("Ignoring requirement that %s complete before %s:"
                  " unmanaged failed resources cannot prevent clone shutdown",
                  input->action->uuid, action->uuid);
         return false;
 
     } else if (pcmk_is_set(input->action->flags, pcmk_action_optional)
                && !pcmk_any_flags_set(input->action->flags,
                                       pcmk_action_always_in_graph
                                       |pcmk_action_added_to_graph)
                && !should_add_action_to_graph(input->action)) {
         crm_trace("Ignoring %s (%d) input %s (%d): "
                   "input optional",
                   action->uuid, action->id,
                   input->action->uuid, input->action->id);
         return false;
     }
 
     crm_trace("%s (%d) input %s %s (%d) on %s should be dumped: %s %s %#.6x",
               action->uuid, action->id, action_type_str(input->action->flags),
               input->action->uuid, input->action->id,
               action_node_str(input->action),
               action_runnable_str(input->action->flags),
               action_optional_str(input->action->flags), input->type);
     return true;
 }
 
 /*!
  * \internal
  * \brief Check whether an ordering creates an ordering loop
  *
  * \param[in]     init_action  "First" action in ordering
  * \param[in]     action       Callers should always set this the same as
  *                             \p init_action (this function may use a different
  *                             value for recursive calls)
  * \param[in,out] input        Action wrapper for "then" action in ordering
  *
  * \return true if the ordering creates a loop, otherwise false
  */
 bool
 pcmk__graph_has_loop(const pe_action_t *init_action, const pe_action_t *action,
                      pe_action_wrapper_t *input)
 {
     bool has_loop = false;
 
-    if (pcmk_is_set(input->action->flags, pe_action_tracking)) {
+    if (pcmk_is_set(input->action->flags, pcmk_action_detect_loop)) {
         crm_trace("Breaking tracking loop: %s@%s -> %s@%s (%#.6x)",
                   input->action->uuid,
                   input->action->node? input->action->node->details->uname : "",
                   action->uuid,
                   action->node? action->node->details->uname : "",
                   input->type);
         return false;
     }
 
     // Don't need to check inputs that won't be used
     if (!should_add_input_to_graph(action, input)) {
         return false;
     }
 
     if (input->action == init_action) {
         crm_debug("Input loop found in %s@%s ->...-> %s@%s",
                   action->uuid,
                   action->node? action->node->details->uname : "",
                   init_action->uuid,
                   init_action->node? init_action->node->details->uname : "");
         return true;
     }
 
-    pe__set_action_flags(input->action, pe_action_tracking);
+    pe__set_action_flags(input->action, pcmk_action_detect_loop);
 
     crm_trace("Checking inputs of action %s@%s input %s@%s (%#.6x)"
               "for graph loop with %s@%s ",
               action->uuid,
               action->node? action->node->details->uname : "",
               input->action->uuid,
               input->action->node? input->action->node->details->uname : "",
               input->type,
               init_action->uuid,
               init_action->node? init_action->node->details->uname : "");
 
     // Recursively check input itself for loops
     for (GList *iter = input->action->actions_before;
          iter != NULL; iter = iter->next) {
 
         if (pcmk__graph_has_loop(init_action, input->action,
                                  (pe_action_wrapper_t *) iter->data)) {
             // Recursive call already logged a debug message
             has_loop = true;
             break;
         }
     }
 
-    pe__clear_action_flags(input->action, pe_action_tracking);
+    pe__clear_action_flags(input->action, pcmk_action_detect_loop);
 
     if (!has_loop) {
         crm_trace("No input loop found in %s@%s -> %s@%s (%#.6x)",
                   input->action->uuid,
                   input->action->node? input->action->node->details->uname : "",
                   action->uuid,
                   action->node? action->node->details->uname : "",
                   input->type);
     }
     return has_loop;
 }
 
 /*!
  * \internal
  * \brief Create a synapse XML element for a transition graph
  *
  * \param[in]     action    Action that synapse is for
  * \param[in,out] data_set  Cluster working set containing graph
  *
  * \return Newly added XML element for new graph synapse
  */
 static xmlNode *
 create_graph_synapse(const pe_action_t *action, pe_working_set_t *data_set)
 {
     int synapse_priority = 0;
     xmlNode *syn = create_xml_node(data_set->graph, "synapse");
 
     crm_xml_add_int(syn, XML_ATTR_ID, data_set->num_synapse);
     data_set->num_synapse++;
 
     if (action->rsc != NULL) {
         synapse_priority = action->rsc->priority;
     }
     if (action->priority > synapse_priority) {
         synapse_priority = action->priority;
     }
     if (synapse_priority > 0) {
         crm_xml_add_int(syn, XML_CIB_ATTR_PRIORITY, synapse_priority);
     }
     return syn;
 }
 
 /*!
  * \internal
  * \brief Add an action to the transition graph XML if appropriate
  *
  * \param[in,out] data       Action to possibly add
  * \param[in,out] user_data  Cluster working set
  *
  * \note This will de-duplicate the action inputs, meaning that the
  *       pe_action_wrapper_t:type flags can no longer be relied on to retain
  *       their original settings. That means this MUST be called after
  *       pcmk__apply_orderings() is complete, and nothing after this should rely
  *       on those type flags. (For example, some code looks for type equal to
  *       some flag rather than whether the flag is set, and some code looks for
  *       particular combinations of flags -- such code must be done before
  *       pcmk__create_graph().)
  */
 static void
 add_action_to_graph(gpointer data, gpointer user_data)
 {
     pe_action_t *action = (pe_action_t *) data;
     pe_working_set_t *data_set = (pe_working_set_t *) user_data;
 
     xmlNode *syn = NULL;
     xmlNode *set = NULL;
     xmlNode *in = NULL;
 
     /* If we haven't already, de-duplicate inputs (even if we won't be adding
      * the action to the graph, so that crm_simulate's dot graphs don't have
      * duplicates).
      */
     if (!pcmk_is_set(action->flags, pe_action_dedup)) {
         pcmk__deduplicate_action_inputs(action);
         pe__set_action_flags(action, pe_action_dedup);
     }
 
     if (pcmk_is_set(action->flags, pcmk_action_added_to_graph)
         || !should_add_action_to_graph(action)) {
         return; // Already added, or shouldn't be
     }
     pe__set_action_flags(action, pcmk_action_added_to_graph);
 
     crm_trace("Adding action %d (%s%s%s) to graph",
               action->id, action->uuid,
               ((action->node == NULL)? "" : " on "),
               ((action->node == NULL)? "" : action->node->details->uname));
 
     syn = create_graph_synapse(action, data_set);
     set = create_xml_node(syn, "action_set");
     in = create_xml_node(syn, "inputs");
 
     create_graph_action(set, action, false, data_set);
 
     for (GList *lpc = action->actions_before; lpc != NULL; lpc = lpc->next) {
         pe_action_wrapper_t *input = (pe_action_wrapper_t *) lpc->data;
 
         if (should_add_input_to_graph(action, input)) {
             xmlNode *input_xml = create_xml_node(in, "trigger");
 
             input->state = pe_link_dumped;
             create_graph_action(input_xml, input->action, true, data_set);
         }
     }
 }
 
 static int transition_id = -1;
 
 /*!
  * \internal
  * \brief Log a message after calculating a transition
  *
  * \param[in] filename  Where transition input is stored
  */
 void
 pcmk__log_transition_summary(const char *filename)
 {
     if (was_processing_error) {
         crm_err("Calculated transition %d (with errors)%s%s",
                 transition_id,
                 (filename == NULL)? "" : ", saving inputs in ",
                 (filename == NULL)? "" : filename);
 
     } else if (was_processing_warning) {
         crm_warn("Calculated transition %d (with warnings)%s%s",
                  transition_id,
                  (filename == NULL)? "" : ", saving inputs in ",
                  (filename == NULL)? "" : filename);
 
     } else {
         crm_notice("Calculated transition %d%s%s",
                    transition_id,
                    (filename == NULL)? "" : ", saving inputs in ",
                    (filename == NULL)? "" : filename);
     }
     if (crm_config_error) {
         crm_notice("Configuration errors found during scheduler processing,"
                    "  please run \"crm_verify -L\" to identify issues");
     }
 }
 
 /*!
  * \internal
  * \brief Add a resource's actions to the transition graph
  *
  * \param[in,out] rsc  Resource whose actions should be added
  */
 void
 pcmk__add_rsc_actions_to_graph(pe_resource_t *rsc)
 {
     GList *iter = NULL;
 
     CRM_ASSERT(rsc != NULL);
     pe_rsc_trace(rsc, "Adding actions for %s to graph", rsc->id);
 
     // First add the resource's own actions
     g_list_foreach(rsc->actions, add_action_to_graph, rsc->cluster);
 
     // Then recursively add its children's actions (appropriate to variant)
     for (iter = rsc->children; iter != NULL; iter = iter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) iter->data;
 
         child_rsc->cmds->add_actions_to_graph(child_rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Create a transition graph with all cluster actions needed
  *
  * \param[in,out] data_set  Cluster working set
  */
 void
 pcmk__create_graph(pe_working_set_t *data_set)
 {
     GList *iter = NULL;
     const char *value = NULL;
     long long limit = 0LL;
 
     transition_id++;
     crm_trace("Creating transition graph %d", transition_id);
 
     data_set->graph = create_xml_node(NULL, XML_TAG_GRAPH);
 
     value = pe_pref(data_set->config_hash, "cluster-delay");
     crm_xml_add(data_set->graph, "cluster-delay", value);
 
     value = pe_pref(data_set->config_hash, "stonith-timeout");
     crm_xml_add(data_set->graph, "stonith-timeout", value);
 
     crm_xml_add(data_set->graph, "failed-stop-offset", "INFINITY");
 
     if (pcmk_is_set(data_set->flags, pcmk_sched_start_failure_fatal)) {
         crm_xml_add(data_set->graph, "failed-start-offset", "INFINITY");
     } else {
         crm_xml_add(data_set->graph, "failed-start-offset", "1");
     }
 
     value = pe_pref(data_set->config_hash, "batch-limit");
     crm_xml_add(data_set->graph, "batch-limit", value);
 
     crm_xml_add_int(data_set->graph, "transition_id", transition_id);
 
     value = pe_pref(data_set->config_hash, "migration-limit");
     if ((pcmk__scan_ll(value, &limit, 0LL) == pcmk_rc_ok) && (limit > 0)) {
         crm_xml_add(data_set->graph, "migration-limit", value);
     }
 
     if (data_set->recheck_by > 0) {
         char *recheck_epoch = NULL;
 
         recheck_epoch = crm_strdup_printf("%llu",
                                           (long long) data_set->recheck_by);
         crm_xml_add(data_set->graph, "recheck-by", recheck_epoch);
         free(recheck_epoch);
     }
 
     /* The following code will de-duplicate action inputs, so nothing past this
      * should rely on the action input type flags retaining their original
      * values.
      */
 
     // Add resource actions to graph
     for (iter = data_set->resources; iter != NULL; iter = iter->next) {
         pe_resource_t *rsc = (pe_resource_t *) iter->data;
 
         pe_rsc_trace(rsc, "Processing actions for %s", rsc->id);
         rsc->cmds->add_actions_to_graph(rsc);
     }
 
     // Add pseudo-action for list of nodes with maintenance state update
     add_maintenance_update(data_set);
 
     // Add non-resource (node) actions
     for (iter = data_set->actions; iter != NULL; iter = iter->next) {
         pe_action_t *action = (pe_action_t *) iter->data;
 
         if ((action->rsc != NULL)
             && (action->node != NULL)
             && action->node->details->shutdown
             && !pcmk_is_set(action->rsc->flags, pcmk_rsc_maintenance)
             && !pcmk_any_flags_set(action->flags,
                                    pcmk_action_optional|pcmk_action_runnable)
             && pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_none)) {
             /* Eventually we should just ignore the 'fence' case, but for now
              * it's the best way to detect (in CTS) when CIB resource updates
              * are being lost.
              */
             if (pcmk_is_set(data_set->flags, pcmk_sched_quorate)
                 || (data_set->no_quorum_policy == pcmk_no_quorum_ignore)) {
                 const bool managed = pcmk_is_set(action->rsc->flags,
                                                  pcmk_rsc_managed);
                 const bool failed = pcmk_is_set(action->rsc->flags,
                                                 pcmk_rsc_failed);
 
                 crm_crit("Cannot %s %s because of %s:%s%s (%s)",
                          action->node->details->unclean? "fence" : "shut down",
                          pe__node_name(action->node), action->rsc->id,
                          (managed? " blocked" : " unmanaged"),
                          (failed? " failed" : ""), action->uuid);
             }
         }
 
         add_action_to_graph((gpointer) action, (gpointer) data_set);
     }
 
     crm_log_xml_trace(data_set->graph, "graph");
 }
diff --git a/lib/pacemaker/pcmk_sched_probes.c b/lib/pacemaker/pcmk_sched_probes.c
index 821ab63972..f452e06381 100644
--- a/lib/pacemaker/pcmk_sched_probes.c
+++ b/lib/pacemaker/pcmk_sched_probes.c
@@ -1,903 +1,903 @@
 /*
  * Copyright 2004-2023 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <glib.h>
 
 #include <crm/crm.h>
 #include <crm/pengine/status.h>
 #include <pacemaker-internal.h>
 #include "libpacemaker_private.h"
 
 /*!
  * \internal
  * \brief Add the expected result to a newly created probe
  *
  * \param[in,out] probe  Probe action to add expected result to
  * \param[in]     rsc    Resource that probe is for
  * \param[in]     node   Node that probe will run on
  */
 static void
 add_expected_result(pe_action_t *probe, const pe_resource_t *rsc,
                     const pe_node_t *node)
 {
     // Check whether resource is currently active on node
     pe_node_t *running = pe_find_node_id(rsc->running_on, node->details->id);
 
     // The expected result is what we think the resource's current state is
     if (running == NULL) {
         pe__add_action_expected_result(probe, CRM_EX_NOT_RUNNING);
 
     } else if (rsc->role == pcmk_role_promoted) {
         pe__add_action_expected_result(probe, CRM_EX_PROMOTED);
     }
 }
 
 /*!
  * \internal
  * \brief Create any needed robes on a node for a list of resources
  *
  * \param[in,out] rscs  List of resources to create probes for
  * \param[in,out] node  Node to create probes on
  *
  * \return true if any probe was created, otherwise false
  */
 bool
 pcmk__probe_resource_list(GList *rscs, pe_node_t *node)
 {
     bool any_created = false;
 
     for (GList *iter = rscs; iter != NULL; iter = iter->next) {
         pe_resource_t *rsc = (pe_resource_t *) iter->data;
 
         if (rsc->cmds->create_probe(rsc, node)) {
             any_created = true;
         }
     }
     return any_created;
 }
 
 /*!
  * \internal
  * \brief Order one resource's start after another's start-up probe
  *
  * \param[in,out] rsc1  Resource that might get start-up probe
  * \param[in]     rsc2  Resource that might be started
  */
 static void
 probe_then_start(pe_resource_t *rsc1, pe_resource_t *rsc2)
 {
     if ((rsc1->allocated_to != NULL)
         && (g_hash_table_lookup(rsc1->known_on,
                                 rsc1->allocated_to->details->id) == NULL)) {
 
         pcmk__new_ordering(rsc1,
                            pcmk__op_key(rsc1->id, PCMK_ACTION_MONITOR, 0),
                            NULL,
                            rsc2, pcmk__op_key(rsc2->id, PCMK_ACTION_START, 0),
                            NULL,
                            pe_order_optional, rsc1->cluster);
     }
 }
 
 /*!
  * \internal
  * \brief Check whether a guest resource will stop
  *
  * \param[in] node  Guest node to check
  *
  * \return true if guest resource will likely stop, otherwise false
  */
 static bool
 guest_resource_will_stop(const pe_node_t *node)
 {
     const pe_resource_t *guest_rsc = node->details->remote_rsc->container;
 
     /* Ideally, we'd check whether the guest has a required stop, but that
      * information doesn't exist yet, so approximate it ...
      */
     return node->details->remote_requires_reset
            || node->details->unclean
            || pcmk_is_set(guest_rsc->flags, pcmk_rsc_failed)
            || (guest_rsc->next_role == pcmk_role_stopped)
 
            // Guest is moving
            || ((guest_rsc->role > pcmk_role_stopped)
                && (guest_rsc->allocated_to != NULL)
                && (pe_find_node(guest_rsc->running_on,
                    guest_rsc->allocated_to->details->uname) == NULL));
 }
 
 /*!
  * \internal
  * \brief Create a probe action for a resource on a node
  *
  * \param[in,out] rsc   Resource to create probe for
  * \param[in,out] node  Node to create probe on
  *
  * \return Newly created probe action
  */
 static pe_action_t *
 probe_action(pe_resource_t *rsc, pe_node_t *node)
 {
     pe_action_t *probe = NULL;
     char *key = pcmk__op_key(rsc->id, PCMK_ACTION_MONITOR, 0);
 
     crm_debug("Scheduling probe of %s %s on %s",
               role2text(rsc->role), rsc->id, pe__node_name(node));
 
     probe = custom_action(rsc, key, PCMK_ACTION_MONITOR, node, FALSE, TRUE,
                           rsc->cluster);
     pe__clear_action_flags(probe, pcmk_action_optional);
 
     pcmk__order_vs_unfence(rsc, node, probe, pe_order_optional);
     add_expected_result(probe, rsc, node);
     return probe;
 }
 
 /*!
  * \internal
  * \brief Create probes for a resource on a node, if needed
  *
  * \brief Schedule any probes needed for a resource on a node
  *
  * \param[in,out] rsc   Resource to create probe for
  * \param[in,out] node  Node to create probe on
  *
  * \return true if any probe was created, otherwise false
  */
 bool
 pcmk__probe_rsc_on_node(pe_resource_t *rsc, pe_node_t *node)
 {
     uint32_t flags = pe_order_optional;
     pe_action_t *probe = NULL;
     pe_node_t *allowed = NULL;
     pe_resource_t *top = uber_parent(rsc);
     const char *reason = NULL;
 
     CRM_ASSERT((rsc != NULL) && (node != NULL));
 
     if (!pcmk_is_set(rsc->cluster->flags, pcmk_sched_probe_resources)) {
         reason = "start-up probes are disabled";
         goto no_probe;
     }
 
     if (pe__is_guest_or_remote_node(node)) {
         const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
 
         if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_none)) {
             reason = "Pacemaker Remote nodes cannot run stonith agents";
             goto no_probe;
 
         } else if (pe__is_guest_node(node)
                    && pe__resource_contains_guest_node(rsc->cluster, rsc)) {
             reason = "guest nodes cannot run resources containing guest nodes";
             goto no_probe;
 
         } else if (rsc->is_remote_node) {
             reason = "Pacemaker Remote nodes cannot host remote connections";
             goto no_probe;
         }
     }
 
     // If this is a collective resource, probes are created for its children
     if (rsc->children != NULL) {
         return pcmk__probe_resource_list(rsc->children, node);
     }
 
     if ((rsc->container != NULL) && !rsc->is_remote_node) {
         reason = "resource is inside a container";
         goto no_probe;
 
     } else if (pcmk_is_set(rsc->flags, pcmk_rsc_removed)) {
         reason = "resource is orphaned";
         goto no_probe;
 
     } else if (g_hash_table_lookup(rsc->known_on, node->details->id) != NULL) {
         reason = "resource state is already known";
         goto no_probe;
     }
 
     allowed = g_hash_table_lookup(rsc->allowed_nodes, node->details->id);
 
     if (rsc->exclusive_discover || top->exclusive_discover) {
         // Exclusive discovery is enabled ...
 
         if (allowed == NULL) {
             // ... but this node is not allowed to run the resource
             reason = "resource has exclusive discovery but is not allowed "
                      "on node";
             goto no_probe;
 
         } else if (allowed->rsc_discover_mode != pe_discover_exclusive) {
             // ... but no constraint marks this node for discovery of resource
             reason = "resource has exclusive discovery but is not enabled "
                      "on node";
             goto no_probe;
         }
     }
 
     if (allowed == NULL) {
         allowed = node;
     }
     if (allowed->rsc_discover_mode == pe_discover_never) {
         reason = "node has discovery disabled";
         goto no_probe;
     }
 
     if (pe__is_guest_node(node)) {
         pe_resource_t *guest = node->details->remote_rsc->container;
 
         if (guest->role == pcmk_role_stopped) {
             // The guest is stopped, so we know no resource is active there
             reason = "node's guest is stopped";
             probe_then_start(guest, top);
             goto no_probe;
 
         } else if (guest_resource_will_stop(node)) {
             reason = "node's guest will stop";
 
             // Order resource start after guest stop (in case it's restarting)
             pcmk__new_ordering(guest,
                                pcmk__op_key(guest->id, PCMK_ACTION_STOP, 0),
                                NULL, top,
                                pcmk__op_key(top->id, PCMK_ACTION_START, 0),
                                NULL, pe_order_optional, rsc->cluster);
             goto no_probe;
         }
     }
 
     // We've eliminated all cases where a probe is not needed, so now it is
     probe = probe_action(rsc, node);
 
     /* Below, we will order the probe relative to start or reload. If this is a
      * clone instance, the start or reload is for the entire clone rather than
      * just the instance. Otherwise, the start or reload is for the resource
      * itself.
      */
     if (!pe_rsc_is_clone(top)) {
         top = rsc;
     }
 
     /* Prevent a start if the resource can't be probed, but don't cause the
      * resource or entire clone to stop if already active.
      */
     if (!pcmk_is_set(probe->flags, pcmk_action_runnable)
         && (top->running_on == NULL)) {
         pe__set_order_flags(flags, pe_order_runnable_left);
     }
 
     // Start or reload after probing the resource
     pcmk__new_ordering(rsc, NULL, probe,
                        top, pcmk__op_key(top->id, PCMK_ACTION_START, 0), NULL,
                        flags, rsc->cluster);
     pcmk__new_ordering(rsc, NULL, probe, top, reload_key(rsc), NULL,
                        pe_order_optional, rsc->cluster);
 
     return true;
 
 no_probe:
     pe_rsc_trace(rsc,
                  "Skipping probe for %s on %s because %s",
                  rsc->id, node->details->id, reason);
     return false;
 }
 
 /*!
  * \internal
  * \brief Check whether a probe should be ordered before another action
  *
  * \param[in] probe  Probe action to check
  * \param[in] then   Other action to check
  *
  * \return true if \p probe should be ordered before \p then, otherwise false
  */
 static bool
 probe_needed_before_action(const pe_action_t *probe, const pe_action_t *then)
 {
     // Probes on a node are performed after unfencing it, not before
     if (pcmk__str_eq(then->task, PCMK_ACTION_STONITH, pcmk__str_none)
         && pe__same_node(probe->node, then->node)) {
         const char *op = g_hash_table_lookup(then->meta, "stonith_action");
 
         if (pcmk__str_eq(op, PCMK_ACTION_ON, pcmk__str_casei)) {
             return false;
         }
     }
 
     // Probes should be done on a node before shutting it down
     if (pcmk__str_eq(then->task, PCMK_ACTION_DO_SHUTDOWN, pcmk__str_none)
         && (probe->node != NULL) && (then->node != NULL)
         && !pe__same_node(probe->node, then->node)) {
         return false;
     }
 
     // Otherwise probes should always be done before any other action
     return true;
 }
 
 /*!
  * \internal
  * \brief Add implicit "probe then X" orderings for "stop then X" orderings
  *
  * If the state of a resource is not known yet, a probe will be scheduled,
  * expecting a "not running" result. If the probe fails, a stop will not be
  * scheduled until the next transition. Thus, if there are ordering constraints
  * like "stop this resource then do something else that's not for the same
  * resource", add implicit "probe this resource then do something" equivalents
  * so the relation is upheld until we know whether a stop is needed.
  *
  * \param[in,out] data_set  Cluster working set
  */
 static void
 add_probe_orderings_for_stops(pe_working_set_t *data_set)
 {
     for (GList *iter = data_set->ordering_constraints; iter != NULL;
          iter = iter->next) {
 
         pe__ordering_t *order = iter->data;
         uint32_t order_flags = pe_order_optional;
         GList *probes = NULL;
         GList *then_actions = NULL;
         pe_action_t *first = NULL;
         pe_action_t *then = NULL;
 
         // Skip disabled orderings
         if (order->flags == pe_order_none) {
             continue;
         }
 
         // Skip non-resource orderings, and orderings for the same resource
         if ((order->lh_rsc == NULL) || (order->lh_rsc == order->rh_rsc)) {
             continue;
         }
 
         // Skip invalid orderings (shouldn't be possible)
         first = order->lh_action;
         then = order->rh_action;
         if (((first == NULL) && (order->lh_action_task == NULL))
             || ((then == NULL) && (order->rh_action_task == NULL))) {
             continue;
         }
 
         // Skip orderings for first actions other than stop
         if ((first != NULL) && !pcmk__str_eq(first->task, PCMK_ACTION_STOP,
                                              pcmk__str_none)) {
             continue;
         } else if ((first == NULL)
                    && !pcmk__ends_with(order->lh_action_task,
                                        "_" PCMK_ACTION_STOP "_0")) {
             continue;
         }
 
         /* Do not imply a probe ordering for a resource inside of a stopping
          * container. Otherwise, it might introduce a transition loop, since a
          * probe could be scheduled after the container starts again.
          */
         if ((order->rh_rsc != NULL)
             && (order->lh_rsc->container == order->rh_rsc)) {
 
             if ((then != NULL) && pcmk__str_eq(then->task, PCMK_ACTION_STOP,
                                                pcmk__str_none)) {
                 continue;
             } else if ((then == NULL)
                        && pcmk__ends_with(order->rh_action_task,
                                           "_" PCMK_ACTION_STOP "_0")) {
                 continue;
             }
         }
 
         // Preserve certain order options for future filtering
         if (pcmk_is_set(order->flags, pe_order_apply_first_non_migratable)) {
             pe__set_order_flags(order_flags,
                                 pe_order_apply_first_non_migratable);
         }
         if (pcmk_is_set(order->flags, pe_order_same_node)) {
             pe__set_order_flags(order_flags, pe_order_same_node);
         }
 
         // Preserve certain order types for future filtering
         if ((order->flags == pe_order_anti_colocation)
             || (order->flags == pe_order_load)) {
             order_flags = order->flags;
         }
 
         // List all scheduled probes for the first resource
         probes = pe__resource_actions(order->lh_rsc, NULL, PCMK_ACTION_MONITOR,
                                       FALSE);
         if (probes == NULL) { // There aren't any
             continue;
         }
 
         // List all relevant "then" actions
         if (then != NULL) {
             then_actions = g_list_prepend(NULL, then);
 
         } else if (order->rh_rsc != NULL) {
             then_actions = find_actions(order->rh_rsc->actions,
                                         order->rh_action_task, NULL);
             if (then_actions == NULL) { // There aren't any
                 g_list_free(probes);
                 continue;
             }
         }
 
         crm_trace("Implying 'probe then' orderings for '%s then %s' "
                   "(id=%d, type=%.6x)",
                   ((first == NULL)? order->lh_action_task : first->uuid),
                   ((then == NULL)? order->rh_action_task : then->uuid),
                   order->id, order->flags);
 
         for (GList *probe_iter = probes; probe_iter != NULL;
              probe_iter = probe_iter->next) {
 
             pe_action_t *probe = (pe_action_t *) probe_iter->data;
 
             for (GList *then_iter = then_actions; then_iter != NULL;
                  then_iter = then_iter->next) {
 
                 pe_action_t *then = (pe_action_t *) then_iter->data;
 
                 if (probe_needed_before_action(probe, then)) {
                     order_actions(probe, then, order_flags);
                 }
             }
         }
 
         g_list_free(then_actions);
         g_list_free(probes);
     }
 }
 
 /*!
  * \internal
  * \brief Add necessary orderings between probe and starts of clone instances
  *
  * , in additon to the ordering with the parent resource added upon creating
  * the probe.
  *
  * \param[in,out] probe     Probe as 'first' action in an ordering
  * \param[in,out] after     'then' action wrapper in the ordering
  */
 static void
 add_start_orderings_for_probe(pe_action_t *probe, pe_action_wrapper_t *after)
 {
     uint32_t flags = pe_order_optional|pe_order_runnable_left;
 
     /* Although the ordering between the probe of the clone instance and the
      * start of its parent has been added in pcmk__probe_rsc_on_node(), we
      * avoided enforcing `pe_order_runnable_left` order type for that as long as
      * any of the clone instances are running to prevent them from being
      * unexpectedly stopped.
      *
      * On the other hand, we still need to prevent any inactive instances from
      * starting unless the probe is runnable so that we don't risk starting too
      * many instances before we know the state on all nodes.
      */
     if ((after->action->rsc->variant <= pcmk_rsc_variant_group)
         || pcmk_is_set(probe->flags, pcmk_action_runnable)
         // The order type is already enforced for its parent.
         || pcmk_is_set(after->type, pe_order_runnable_left)
         || (pe__const_top_resource(probe->rsc, false) != after->action->rsc)
         || !pcmk__str_eq(after->action->task, PCMK_ACTION_START,
                          pcmk__str_none)) {
         return;
     }
 
     crm_trace("Adding probe start orderings for 'unrunnable %s@%s "
               "then instances of %s@%s'",
               probe->uuid, pe__node_name(probe->node),
               after->action->uuid, pe__node_name(after->action->node));
 
     for (GList *then_iter = after->action->actions_after; then_iter != NULL;
          then_iter = then_iter->next) {
 
         pe_action_wrapper_t *then = (pe_action_wrapper_t *) then_iter->data;
 
         if (then->action->rsc->running_on
             || (pe__const_top_resource(then->action->rsc, false)
                 != after->action->rsc)
             || !pcmk__str_eq(then->action->task, PCMK_ACTION_START,
                              pcmk__str_none)) {
             continue;
         }
 
         crm_trace("Adding probe start ordering for 'unrunnable %s@%s "
                   "then %s@%s' (type=%#.6x)",
                   probe->uuid, pe__node_name(probe->node),
                   then->action->uuid, pe__node_name(then->action->node), flags);
 
         /* Prevent the instance from starting if the instance can't, but don't
          * cause any other intances to stop if already active.
          */
         order_actions(probe, then->action, flags);
     }
 
     return;
 }
 
 /*!
  * \internal
  * \brief Order probes before restarts and re-promotes
  *
  * If a given ordering is a "probe then start" or "probe then promote" ordering,
  * add an implicit "probe then stop/demote" ordering in case the action is part
  * of a restart/re-promote, and do the same recursively for all actions ordered
  * after the "then" action.
  *
  * \param[in,out] probe     Probe as 'first' action in an ordering
  * \param[in,out] after     'then' action in the ordering
  */
 static void
 add_restart_orderings_for_probe(pe_action_t *probe, pe_action_t *after)
 {
     GList *iter = NULL;
     bool interleave = false;
     pe_resource_t *compatible_rsc = NULL;
 
     // Validate that this is a resource probe followed by some action
     if ((after == NULL) || (probe == NULL) || (probe->rsc == NULL)
         || (probe->rsc->variant != pcmk_rsc_variant_primitive)
         || !pcmk__str_eq(probe->task, PCMK_ACTION_MONITOR, pcmk__str_none)) {
         return;
     }
 
     // Avoid running into any possible loop
-    if (pcmk_is_set(after->flags, pe_action_tracking)) {
+    if (pcmk_is_set(after->flags, pcmk_action_detect_loop)) {
         return;
     }
-    pe__set_action_flags(after, pe_action_tracking);
+    pe__set_action_flags(after, pcmk_action_detect_loop);
 
     crm_trace("Adding probe restart orderings for '%s@%s then %s@%s'",
               probe->uuid, pe__node_name(probe->node),
               after->uuid, pe__node_name(after->node));
 
     /* Add restart orderings if "then" is for a different primitive.
      * Orderings for collective resources will be added later.
      */
     if ((after->rsc != NULL)
         && (after->rsc->variant == pcmk_rsc_variant_primitive)
         && (probe->rsc != after->rsc)) {
 
             GList *then_actions = NULL;
 
             if (pcmk__str_eq(after->task, PCMK_ACTION_START, pcmk__str_none)) {
                 then_actions = pe__resource_actions(after->rsc, NULL,
                                                     PCMK_ACTION_STOP, FALSE);
 
             } else if (pcmk__str_eq(after->task, PCMK_ACTION_PROMOTE,
                                     pcmk__str_none)) {
                 then_actions = pe__resource_actions(after->rsc, NULL,
                                                     PCMK_ACTION_DEMOTE, FALSE);
             }
 
             for (iter = then_actions; iter != NULL; iter = iter->next) {
                 pe_action_t *then = (pe_action_t *) iter->data;
 
                 // Skip pseudo-actions (for example, those implied by fencing)
                 if (!pcmk_is_set(then->flags, pcmk_action_pseudo)) {
                     order_actions(probe, then, pe_order_optional);
                 }
             }
             g_list_free(then_actions);
     }
 
     /* Detect whether "then" is an interleaved clone action. For these, we want
      * to add orderings only for the relevant instance.
      */
     if ((after->rsc != NULL)
         && (after->rsc->variant > pcmk_rsc_variant_group)) {
         const char *interleave_s = g_hash_table_lookup(after->rsc->meta,
                                                        XML_RSC_ATTR_INTERLEAVE);
 
         interleave = crm_is_true(interleave_s);
         if (interleave) {
             compatible_rsc = pcmk__find_compatible_instance(probe->rsc,
                                                             after->rsc,
                                                             pcmk_role_unknown,
                                                             false);
         }
     }
 
     /* Now recursively do the same for all actions ordered after "then". This
      * also handles collective resources since the collective action will be
      * ordered before its individual instances' actions.
      */
     for (iter = after->actions_after; iter != NULL; iter = iter->next) {
         pe_action_wrapper_t *after_wrapper = (pe_action_wrapper_t *) iter->data;
 
         /* pe_order_implies_then is the reason why a required A.start
          * implies/enforces B.start to be required too, which is the cause of
          * B.restart/re-promote.
          *
          * Not sure about pe_order_implies_then_on_node though. It's now only
          * used for unfencing case, which tends to introduce transition
          * loops...
          */
         if (!pcmk_is_set(after_wrapper->type, pe_order_implies_then)) {
             /* The order type between a group/clone and its child such as
              * B.start-> B_child.start is:
              * pe_order_implies_first_printed | pe_order_runnable_left
              *
              * Proceed through the ordering chain and build dependencies with
              * its children.
              */
             if ((after->rsc == NULL)
                 || (after->rsc->variant < pcmk_rsc_variant_group)
                 || (probe->rsc->parent == after->rsc)
                 || (after_wrapper->action->rsc == NULL)
                 || (after_wrapper->action->rsc->variant > pcmk_rsc_variant_group)
                 || (after->rsc != after_wrapper->action->rsc->parent)) {
                 continue;
             }
 
             /* Proceed to the children of a group or a non-interleaved clone.
              * For an interleaved clone, proceed only to the relevant child.
              */
             if ((after->rsc->variant > pcmk_rsc_variant_group) && interleave
                 && ((compatible_rsc == NULL)
                     || (compatible_rsc != after_wrapper->action->rsc))) {
                 continue;
             }
         }
 
         crm_trace("Recursively adding probe restart orderings for "
                   "'%s@%s then %s@%s' (type=%#.6x)",
                   after->uuid, pe__node_name(after->node),
                   after_wrapper->action->uuid,
                   pe__node_name(after_wrapper->action->node),
                   after_wrapper->type);
 
         add_restart_orderings_for_probe(probe, after_wrapper->action);
     }
 }
 
 /*!
  * \internal
  * \brief Clear the tracking flag on all scheduled actions
  *
  * \param[in,out] data_set  Cluster working set
  */
 static void
 clear_actions_tracking_flag(pe_working_set_t *data_set)
 {
     for (GList *iter = data_set->actions; iter != NULL; iter = iter->next) {
         pe_action_t *action = iter->data;
 
-        pe__clear_action_flags(action, pe_action_tracking);
+        pe__clear_action_flags(action, pcmk_action_detect_loop);
     }
 }
 
 /*!
  * \internal
  * \brief Add start and restart orderings for probes scheduled for a resource
  *
  * \param[in,out] data       Resource whose probes should be ordered
  * \param[in]     user_data  Unused
  */
 static void
 add_start_restart_orderings_for_rsc(gpointer data, gpointer user_data)
 {
     pe_resource_t *rsc = data;
     GList *probes = NULL;
 
     // For collective resources, order each instance recursively
     if (rsc->variant != pcmk_rsc_variant_primitive) {
         g_list_foreach(rsc->children, add_start_restart_orderings_for_rsc,
                        NULL);
         return;
     }
 
     // Find all probes for given resource
     probes = pe__resource_actions(rsc, NULL, PCMK_ACTION_MONITOR, FALSE);
 
     // Add probe restart orderings for each probe found
     for (GList *iter = probes; iter != NULL; iter = iter->next) {
         pe_action_t *probe = (pe_action_t *) iter->data;
 
         for (GList *then_iter = probe->actions_after; then_iter != NULL;
              then_iter = then_iter->next) {
 
             pe_action_wrapper_t *then = (pe_action_wrapper_t *) then_iter->data;
 
             add_start_orderings_for_probe(probe, then);
             add_restart_orderings_for_probe(probe, then->action);
             clear_actions_tracking_flag(rsc->cluster);
         }
     }
 
     g_list_free(probes);
 }
 
 /*!
  * \internal
  * \brief Add "A then probe B" orderings for "A then B" orderings
  *
  * \param[in,out] data_set  Cluster working set
  *
  * \note This function is currently disabled (see next comment).
  */
 static void
 order_then_probes(pe_working_set_t *data_set)
 {
 #if 0
     /* Given an ordering "A then B", we would prefer to wait for A to be started
      * before probing B.
      *
      * For example, if A is a filesystem which B can't even run without, it
      * would be helpful if the author of B's agent could assume that A is
      * running before B.monitor will be called.
      *
      * However, we can't _only_ probe after A is running, otherwise we wouldn't
      * detect the state of B if A could not be started. We can't even do an
      * opportunistic version of this, because B may be moving:
      *
      *   A.stop -> A.start -> B.probe -> B.stop -> B.start
      *
      * and if we add B.stop -> A.stop here, we get a loop:
      *
      *   A.stop -> A.start -> B.probe -> B.stop -> A.stop
      *
      * We could kill the "B.probe -> B.stop" dependency, but that could mean
      * stopping B "too" soon, because B.start must wait for the probe, and
      * we don't want to stop B if we can't start it.
      *
      * We could add the ordering only if A is an anonymous clone with
      * clone-max == node-max (since we'll never be moving it). However, we could
      * still be stopping one instance at the same time as starting another.
      *
      * The complexity of checking for allowed conditions combined with the ever
      * narrowing use case suggests that this code should remain disabled until
      * someone gets smarter.
      */
     for (GList *iter = data_set->resources; iter != NULL; iter = iter->next) {
         pe_resource_t *rsc = (pe_resource_t *) iter->data;
 
         pe_action_t *start = NULL;
         GList *actions = NULL;
         GList *probes = NULL;
 
         actions = pe__resource_actions(rsc, NULL, PCMK_ACTION_START, FALSE);
 
         if (actions) {
             start = actions->data;
             g_list_free(actions);
         }
 
         if (start == NULL) {
             crm_err("No start action for %s", rsc->id);
             continue;
         }
 
         probes = pe__resource_actions(rsc, NULL, PCMK_ACTION_MONITOR, FALSE);
 
         for (actions = start->actions_before; actions != NULL;
              actions = actions->next) {
 
             pe_action_wrapper_t *before = (pe_action_wrapper_t *) actions->data;
 
             pe_action_t *first = before->action;
             pe_resource_t *first_rsc = first->rsc;
 
             if (first->required_runnable_before) {
                 for (GList *clone_actions = first->actions_before;
                      clone_actions != NULL;
                      clone_actions = clone_actions->next) {
 
                     before = (pe_action_wrapper_t *) clone_actions->data;
 
                     crm_trace("Testing '%s then %s' for %s",
                               first->uuid, before->action->uuid, start->uuid);
 
                     CRM_ASSERT(before->action->rsc != NULL);
                     first_rsc = before->action->rsc;
                     break;
                 }
 
             } else if (!pcmk__str_eq(first->task, PCMK_ACTION_START,
                                      pcmk__str_none)) {
                 crm_trace("Not a start op %s for %s", first->uuid, start->uuid);
             }
 
             if (first_rsc == NULL) {
                 continue;
 
             } else if (pe__const_top_resource(first_rsc, false)
                        == pe__const_top_resource(start->rsc, false)) {
                 crm_trace("Same parent %s for %s", first_rsc->id, start->uuid);
                 continue;
 
             } else if (!pe_rsc_is_clone(pe__const_top_resource(first_rsc,
                                                                false))) {
                 crm_trace("Not a clone %s for %s", first_rsc->id, start->uuid);
                 continue;
             }
 
             crm_err("Applying %s before %s %d", first->uuid, start->uuid,
                     pe__const_top_resource(first_rsc, false)->variant);
 
             for (GList *probe_iter = probes; probe_iter != NULL;
                  probe_iter = probe_iter->next) {
 
                 pe_action_t *probe = (pe_action_t *) probe_iter->data;
 
                 crm_err("Ordering %s before %s", first->uuid, probe->uuid);
                 order_actions(first, probe, pe_order_optional);
             }
         }
     }
 #endif
 }
 
 void
 pcmk__order_probes(pe_working_set_t *data_set)
 {
     // Add orderings for "probe then X"
     g_list_foreach(data_set->resources, add_start_restart_orderings_for_rsc,
                    NULL);
     add_probe_orderings_for_stops(data_set);
 
     order_then_probes(data_set);
 }
 
 /*!
  * \internal
  * \brief Schedule any probes needed
  *
  * \param[in,out] data_set  Cluster working set
  *
  * \note This may also schedule fencing of failed remote nodes.
  */
 void
 pcmk__schedule_probes(pe_working_set_t *data_set)
 {
     // Schedule probes on each node in the cluster as needed
     for (GList *iter = data_set->nodes; iter != NULL; iter = iter->next) {
         pe_node_t *node = (pe_node_t *) iter->data;
         const char *probed = NULL;
 
         if (!node->details->online) { // Don't probe offline nodes
             if (pcmk__is_failed_remote_node(node)) {
                 pe_fence_node(data_set, node,
                               "the connection is unrecoverable", FALSE);
             }
             continue;
 
         } else if (node->details->unclean) { // ... or nodes that need fencing
             continue;
 
         } else if (!node->details->rsc_discovery_enabled) {
             // The user requested that probes not be done on this node
             continue;
         }
 
         /* This is no longer needed for live clusters, since the probe_complete
          * node attribute will never be in the CIB. However this is still useful
          * for processing old saved CIBs (< 1.1.14), including the
          * reprobe-target_rc regression test.
          */
         probed = pe_node_attribute_raw(node, CRM_OP_PROBED);
         if (probed != NULL && crm_is_true(probed) == FALSE) {
             pe_action_t *probe_op = NULL;
 
             probe_op = custom_action(NULL,
                                      crm_strdup_printf("%s-%s", CRM_OP_REPROBE,
                                                        node->details->uname),
                                      CRM_OP_REPROBE, node, FALSE, TRUE,
                                      data_set);
             add_hash_param(probe_op->meta, XML_ATTR_TE_NOWAIT,
                            XML_BOOLEAN_TRUE);
             continue;
         }
 
         // Probe each resource in the cluster on this node, as needed
         pcmk__probe_resource_list(data_set->resources, node);
     }
 }