diff --git a/include/crm/common/xml_names_internal.h b/include/crm/common/xml_names_internal.h
index 54b8855b23..1f8b438730 100644
--- a/include/crm/common/xml_names_internal.h
+++ b/include/crm/common/xml_names_internal.h
@@ -1,290 +1,291 @@
 /*
  * Copyright 2004-2024 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_XML_NAMES_INTERNAL__H
 #define PCMK__CRM_COMMON_XML_NAMES_INTERNAL__H
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /*
  * XML element names used only by internal code
  */
 
 #define PCMK__XE_ACK                    "ack"
 #define PCMK__XE_ACTION_SET             "action_set"
 #define PCMK__XE_ATTRIBUTES             "attributes"
 #define PCMK__XE_CIB_CALLBACK           "cib-callback"
 #define PCMK__XE_CIB_CALLDATA           "cib_calldata"
 #define PCMK__XE_CIB_COMMAND            "cib_command"
 #define PCMK__XE_CIB_REPLY              "cib-reply"
 #define PCMK__XE_CIB_RESULT             "cib_result"
 #define PCMK__XE_CIB_TRANSACTION        "cib_transaction"
 #define PCMK__XE_CIB_UPDATE_RESULT      "cib_update_result"
 #define PCMK__XE_COPY                   "copy"
 #define PCMK__XE_CRM_EVENT              "crm_event"
 #define PCMK__XE_CRM_XML                "crm_xml"
 #define PCMK__XE_DIV                    "div"
 #define PCMK__XE_DOWNED                 "downed"
 #define PCMK__XE_EXIT_NOTIFICATION      "exit-notification"
 #define PCMK__XE_FAILED_UPDATE          "failed_update"
 #define PCMK__XE_GENERATION_TUPLE       "generation_tuple"
 #define PCMK__XE_INPUTS                 "inputs"
 #define PCMK__XE_LRM                    "lrm"
 #define PCMK__XE_LRM_RESOURCE           "lrm_resource"
 #define PCMK__XE_LRM_RESOURCES          "lrm_resources"
 #define PCMK__XE_LRM_RSC_OP             "lrm_rsc_op"
 #define PCMK__XE_LRMD_ALERT             "lrmd_alert"
 #define PCMK__XE_LRMD_CALLDATA          "lrmd_calldata"
 #define PCMK__XE_LRMD_COMMAND           "lrmd_command"
 #define PCMK__XE_LRMD_IPC_MSG           "lrmd_ipc_msg"
 #define PCMK__XE_LRMD_IPC_PROXY         "lrmd_ipc_proxy"
 #define PCMK__XE_LRMD_NOTIFY            "lrmd_notify"
 #define PCMK__XE_LRMD_REPLY             "lrmd_reply"
 #define PCMK__XE_LRMD_RSC               "lrmd_rsc"
 #define PCMK__XE_LRMD_RSC_OP            "lrmd_rsc_op"
 #define PCMK__XE_MAINTENANCE            "maintenance"
 #define PCMK__XE_MESSAGE                "message"
 #define PCMK__XE_META                   "meta"
 #define PCMK__XE_NACK                   "nack"
 #define PCMK__XE_NODE_STATE             "node_state"
 #define PCMK__XE_NOTIFY                 "notify"
 #define PCMK__XE_OPTIONS                "options"
 #define PCMK__XE_PARAM                  "param"
 #define PCMK__XE_PING                   "ping"
 #define PCMK__XE_PING_RESPONSE          "ping_response"
 #define PCMK__XE_PSEUDO_EVENT           "pseudo_event"
 #define PCMK__XE_RESOURCE_SETTINGS      "resource-settings"
 #define PCMK__XE_RSC_OP                 "rsc_op"
 #define PCMK__XE_SHUTDOWN               "shutdown"
 #define PCMK__XE_SPAN                   "span"
 #define PCMK__XE_ST_ASYNC_TIMEOUT_VALUE "st-async-timeout-value"
 #define PCMK__XE_ST_CALLDATA            "st_calldata"
 #define PCMK__XE_ST_DEVICE_ACTION       "st_device_action"
 #define PCMK__XE_ST_DEVICE_ID           "st_device_id"
 #define PCMK__XE_ST_HISTORY             "st_history"
 #define PCMK__XE_ST_NOTIFY_FENCE        "st_notify_fence"
 #define PCMK__XE_ST_REPLY               "st-reply"
 #define PCMK__XE_STONITH_COMMAND        "stonith_command"
+#define PCMK__XE_SYNAPSE                "synapse"
 #define PCMK__XE_TICKET_STATE           "ticket_state"
 #define PCMK__XE_TRANSIENT_ATTRIBUTES   "transient_attributes"
 #define PCMK__XE_TRANSITION_GRAPH       "transition_graph"
 #define PCMK__XE_TRIGGER                "trigger"
 #define PCMK__XE_XPATH_QUERY            "xpath-query"
 #define PCMK__XE_XPATH_QUERY_PATH       "xpath-query-path"
 
 /* @COMPAT Deprecate somehow. It's undocumented and behaves the same as
  * PCMK__XE_CIB in places where it's recognized.
  */
 #define PCMK__XE_ALL                    "all"
 
 // @COMPAT Deprecated since 2.1.8
 #define PCMK__XE_FAILED                 "failed"
 
 
 /*
  * XML attribute names used only by internal code
  */
 
 #define PCMK__XA_ACL_TARGET             "acl_target"
 #define PCMK__XA_ATTR_CLEAR_INTERVAL    "attr_clear_interval"
 #define PCMK__XA_ATTR_CLEAR_OPERATION   "attr_clear_operation"
 #define PCMK__XA_ATTR_DAMPENING         "attr_dampening"
 #define PCMK__XA_ATTR_HOST              "attr_host"
 #define PCMK__XA_ATTR_HOST_ID           "attr_host_id"
 #define PCMK__XA_ATTR_IS_PRIVATE        "attr_is_private"
 #define PCMK__XA_ATTR_IS_REMOTE         "attr_is_remote"
 #define PCMK__XA_ATTR_NAME              "attr_name"
 #define PCMK__XA_ATTR_REGEX             "attr_regex"
 #define PCMK__XA_ATTR_RESOURCE          "attr_resource"
 #define PCMK__XA_ATTR_SECTION           "attr_section"
 #define PCMK__XA_ATTR_SET               "attr_set"
 #define PCMK__XA_ATTR_SET_TYPE          "attr_set_type"
 #define PCMK__XA_ATTR_SYNC_POINT        "attr_sync_point"
 #define PCMK__XA_ATTR_USER              "attr_user"
 #define PCMK__XA_ATTR_VALUE             "attr_value"
 #define PCMK__XA_ATTR_VERSION           "attr_version"
 #define PCMK__XA_ATTR_WRITER            "attr_writer"
 #define PCMK__XA_ATTRD_IS_FORCE_WRITE   "attrd_is_force_write"
 #define PCMK__XA_CALL_ID                "call-id"
 #define PCMK__XA_CIB_CALLID             "cib_callid"
 #define PCMK__XA_CIB_CALLOPT            "cib_callopt"
 #define PCMK__XA_CIB_CLIENTID           "cib_clientid"
 #define PCMK__XA_CIB_CLIENTNAME         "cib_clientname"
 #define PCMK__XA_CIB_DELEGATED_FROM     "cib_delegated_from"
 #define PCMK__XA_CIB_HOST               "cib_host"
 #define PCMK__XA_CIB_ISREPLYTO          "cib_isreplyto"
 #define PCMK__XA_CIB_NOTIFY_ACTIVATE    "cib_notify_activate"
 #define PCMK__XA_CIB_NOTIFY_TYPE        "cib_notify_type"
 #define PCMK__XA_CIB_OP                 "cib_op"
 #define PCMK__XA_CIB_PING_ID            "cib_ping_id"
 #define PCMK__XA_CIB_RC                 "cib_rc"
 #define PCMK__XA_CIB_SCHEMA_MAX         "cib_schema_max"
 #define PCMK__XA_CIB_SECTION            "cib_section"
 #define PCMK__XA_CIB_UPDATE             "cib_update"
 #define PCMK__XA_CIB_UPGRADE_RC         "cib_upgrade_rc"
 #define PCMK__XA_CIB_USER               "cib_user"
 #define PCMK__XA_CLIENT_NAME            "client_name"
 #define PCMK__XA_CLIENT_UUID            "client_uuid"
 #define PCMK__XA_CONFIRM                "confirm"
 #define PCMK__XA_CONNECTION_HOST        "connection_host"
 #define PCMK__XA_CONTENT                "content"
 #define PCMK__XA_CRMD_STATE             "crmd_state"
 #define PCMK__XA_CRM_HOST_TO            "crm_host_to"
 #define PCMK__XA_CRM_LIMIT_MAX          "crm-limit-max"
 #define PCMK__XA_CRM_LIMIT_MODE         "crm-limit-mode"
 #define PCMK__XA_CRM_SUBSYSTEM          "crm_subsystem"
 #define PCMK__XA_CRM_SYS_FROM           "crm_sys_from"
 #define PCMK__XA_CRM_SYS_TO             "crm_sys_to"
 #define PCMK__XA_CRM_TASK               "crm_task"
 #define PCMK__XA_CRM_TGRAPH_IN          "crm-tgraph-in"
 #define PCMK__XA_DC_LEAVING             "dc-leaving"
 #define PCMK__XA_DIGEST                 "digest"
 #define PCMK__XA_ELECTION_AGE_SEC       "election-age-sec"
 #define PCMK__XA_ELECTION_AGE_NANO_SEC  "election-age-nano-sec"
 #define PCMK__XA_ELECTION_ID            "election-id"
 #define PCMK__XA_ELECTION_OWNER         "election-owner"
 #define PCMK__XA_GRANTED                "granted"
 #define PCMK__XA_HIDDEN                 "hidden"
 #define PCMK__XA_HTTP_EQUIV             "http-equiv"
 #define PCMK__XA_IN_CCM                 "in_ccm"
 #define PCMK__XA_IPC_PROTO_VERSION      "ipc-protocol-version"
 #define PCMK__XA_JOIN                   "join"
 #define PCMK__XA_JOIN_ID                "join_id"
 #define PCMK__XA_LINE                   "line"
 #define PCMK__XA_LONG_ID                "long-id"
 #define PCMK__XA_LRMD_ALERT_ID          "lrmd_alert_id"
 #define PCMK__XA_LRMD_ALERT_PATH        "lrmd_alert_path"
 #define PCMK__XA_LRMD_CALLID            "lrmd_callid"
 #define PCMK__XA_LRMD_CALLOPT           "lrmd_callopt"
 #define PCMK__XA_LRMD_CLASS             "lrmd_class"
 #define PCMK__XA_LRMD_CLIENTID          "lrmd_clientid"
 #define PCMK__XA_LRMD_CLIENTNAME        "lrmd_clientname"
 #define PCMK__XA_LRMD_EXEC_OP_STATUS    "lrmd_exec_op_status"
 #define PCMK__XA_LRMD_EXEC_RC           "lrmd_exec_rc"
 #define PCMK__XA_LRMD_EXEC_TIME         "lrmd_exec_time"
 #define PCMK__XA_LRMD_IPC_CLIENT        "lrmd_ipc_client"
 #define PCMK__XA_LRMD_IPC_MSG_FLAGS     "lrmd_ipc_msg_flags"
 #define PCMK__XA_LRMD_IPC_MSG_ID        "lrmd_ipc_msg_id"
 #define PCMK__XA_LRMD_IPC_OP            "lrmd_ipc_op"
 #define PCMK__XA_LRMD_IPC_SERVER        "lrmd_ipc_server"
 #define PCMK__XA_LRMD_IPC_SESSION       "lrmd_ipc_session"
 #define PCMK__XA_LRMD_IS_IPC_PROVIDER   "lrmd_is_ipc_provider"
 #define PCMK__XA_LRMD_OP                "lrmd_op"
 #define PCMK__XA_LRMD_ORIGIN            "lrmd_origin"
 #define PCMK__XA_LRMD_PROTOCOL_VERSION  "lrmd_protocol_version"
 #define PCMK__XA_LRMD_PROVIDER          "lrmd_provider"
 #define PCMK__XA_LRMD_QUEUE_TIME        "lrmd_queue_time"
 #define PCMK__XA_LRMD_RC                "lrmd_rc"
 #define PCMK__XA_LRMD_RCCHANGE_TIME     "lrmd_rcchange_time"
 #define PCMK__XA_LRMD_REMOTE_MSG_ID     "lrmd_remote_msg_id"
 #define PCMK__XA_LRMD_REMOTE_MSG_TYPE   "lrmd_remote_msg_type"
 #define PCMK__XA_LRMD_RSC_ACTION        "lrmd_rsc_action"
 #define PCMK__XA_LRMD_RSC_DELETED       "lrmd_rsc_deleted"
 #define PCMK__XA_LRMD_RSC_EXIT_REASON   "lrmd_rsc_exit_reason"
 #define PCMK__XA_LRMD_RSC_ID            "lrmd_rsc_id"
 #define PCMK__XA_LRMD_RSC_INTERVAL      "lrmd_rsc_interval"
 #define PCMK__XA_LRMD_RSC_OUTPUT        "lrmd_rsc_output"
 #define PCMK__XA_LRMD_RSC_START_DELAY   "lrmd_rsc_start_delay"
 #define PCMK__XA_LRMD_RSC_USERDATA_STR  "lrmd_rsc_userdata_str"
 #define PCMK__XA_LRMD_RUN_TIME          "lrmd_run_time"
 #define PCMK__XA_LRMD_TIMEOUT           "lrmd_timeout"
 #define PCMK__XA_LRMD_TYPE              "lrmd_type"
 #define PCMK__XA_LRMD_WATCHDOG          "lrmd_watchdog"
 #define PCMK__XA_MAJOR_VERSION          "major_version"
 #define PCMK__XA_MINOR_VERSION          "minor_version"
 #define PCMK__XA_MODE                   "mode"
 #define PCMK__XA_NAMESPACE              "namespace"
 #define PCMK__XA_NODE_FENCED            "node_fenced"
 #define PCMK__XA_NODE_IN_MAINTENANCE    "node_in_maintenance"
 #define PCMK__XA_NODE_START_STATE       "node_start_state"
 #define PCMK__XA_NODE_STATE             "node_state"
 #define PCMK__XA_OP_DIGEST              "op-digest"
 #define PCMK__XA_OP_FORCE_RESTART       "op-force-restart"
 #define PCMK__XA_OP_RESTART_DIGEST      "op-restart-digest"
 #define PCMK__XA_OP_SECURE_DIGEST       "op-secure-digest"
 #define PCMK__XA_OP_SECURE_PARAMS       "op-secure-params"
 #define PCMK__XA_OP_STATUS              "op-status"
 #define PCMK__XA_OPERATION_KEY          "operation_key"
 #define PCMK__XA_ORIGINAL_CIB_OP        "original_cib_op"
 #define PCMK__XA_PACEMAKERD_STATE       "pacemakerd_state"
 #define PCMK__XA_PASSWORD               "password"
 #define PCMK__XA_PRIORITY               "priority"
 #define PCMK__XA_RC_CODE                "rc-code"
 #define PCMK__XA_REAP                   "reap"
 
 /* Actions to be executed on Pacemaker Remote nodes are routed through the
  * controller on the cluster node hosting the remote connection. That cluster
  * node is considered the router node for the action.
  */
 #define PCMK__XA_ROUTER_NODE            "router_node"
 
 #define PCMK__XA_RSC_ID                 "rsc-id"
 #define PCMK__XA_RSC_PROVIDES           "rsc_provides"
 #define PCMK__XA_SCHEMA                 "schema"
 #define PCMK__XA_SCHEMAS                "schemas"
 #define PCMK__XA_SET                    "set"
 #define PCMK__XA_SRC                    "src"
 #define PCMK__XA_ST_ACTION_DISALLOWED   "st_action_disallowed"
 #define PCMK__XA_ST_ACTION_TIMEOUT      "st_action_timeout"
 #define PCMK__XA_ST_AVAILABLE_DEVICES   "st-available-devices"
 #define PCMK__XA_ST_CALLID              "st_callid"
 #define PCMK__XA_ST_CALLOPT             "st_callopt"
 #define PCMK__XA_ST_CLIENTID            "st_clientid"
 #define PCMK__XA_ST_CLIENTNAME          "st_clientname"
 #define PCMK__XA_ST_CLIENTNODE          "st_clientnode"
 #define PCMK__XA_ST_DATE                "st_date"
 #define PCMK__XA_ST_DATE_NSEC           "st_date_nsec"
 #define PCMK__XA_ST_DELAY               "st_delay"
 #define PCMK__XA_ST_DELAY_BASE          "st_delay_base"
 #define PCMK__XA_ST_DELAY_MAX           "st_delay_max"
 #define PCMK__XA_ST_DELEGATE            "st_delegate"
 #define PCMK__XA_ST_DEVICE_ACTION       "st_device_action"
 #define PCMK__XA_ST_DEVICE_ID           "st_device_id"
 #define PCMK__XA_ST_DEVICE_SUPPORT_FLAGS    "st_device_support_flags"
 #define PCMK__XA_ST_DIFFERENTIAL        "st_differential"
 #define PCMK__XA_ST_MONITOR_VERIFIED    "st_monitor_verified"
 #define PCMK__XA_ST_NOTIFY_ACTIVATE     "st_notify_activate"
 #define PCMK__XA_ST_NOTIFY_DEACTIVATE   "st_notify_deactivate"
 #define PCMK__XA_ST_OP                  "st_op"
 #define PCMK__XA_ST_OP_MERGED           "st_op_merged"
 #define PCMK__XA_ST_ORIGIN              "st_origin"
 #define PCMK__XA_ST_OUTPUT              "st_output"
 #define PCMK__XA_ST_RC                  "st_rc"
 #define PCMK__XA_ST_REMOTE_OP           "st_remote_op"
 #define PCMK__XA_ST_REMOTE_OP_RELAY     "st_remote_op_relay"
 #define PCMK__XA_ST_REQUIRED            "st_required"
 #define PCMK__XA_ST_STATE               "st_state"
 #define PCMK__XA_ST_TARGET              "st_target"
 #define PCMK__XA_ST_TIMEOUT             "st_timeout"
 #define PCMK__XA_ST_TOLERANCE           "st_tolerance"
 #define PCMK__XA_SUBT                   "subt"                  // subtype
 #define PCMK__XA_T                      "t"                     // type
 #define PCMK__XA_TRANSITION_KEY         "transition-key"
 #define PCMK__XA_TRANSITION_MAGIC       "transition-magic"
 #define PCMK__XA_UPTIME                 "uptime"
 
 // @COMPAT Deprecated since 2.1.7
 #define PCMK__XA_ORDERING               "ordering"
 
 // @COMPAT Deprecated alias for PCMK_XA_PROMOTED_ONLY since 2.0.0
 #define PCMK__XA_PROMOTED_ONLY_LEGACY   "master_only"
 
 // @COMPAT Deprecated since 2.1.6
 #define PCMK__XA_REPLACE                "replace"
 
 // @COMPAT Deprecated alias for \c PCMK_XA_AUTOMATIC since 1.1.14
 #define PCMK__XA_REQUIRED               "required"
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif // PCMK__CRM_COMMON_XML_NAMES_INTERNAL__H
diff --git a/lib/pacemaker/pcmk_graph_consumer.c b/lib/pacemaker/pcmk_graph_consumer.c
index f80b3042c5..b0735e38da 100644
--- a/lib/pacemaker/pcmk_graph_consumer.c
+++ b/lib/pacemaker/pcmk_graph_consumer.c
@@ -1,868 +1,868 @@
 /*
  * Copyright 2004-2024 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.
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 #include <sys/stat.h>
 
 #include <crm/crm.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 #include <crm/lrmd_internal.h>
 #include <pacemaker-internal.h>
 
 
 /*
  * Functions for freeing transition graph objects
  */
 
 /*!
  * \internal
  * \brief Free a transition graph action object
  *
  * \param[in,out] user_data  Action to free
  */
 static void
 free_graph_action(gpointer user_data)
 {
     pcmk__graph_action_t *action = user_data;
 
     if (action->timer != 0) {
         crm_warn("Cancelling timer for graph action %d", action->id);
         g_source_remove(action->timer);
     }
     if (action->params != NULL) {
         g_hash_table_destroy(action->params);
     }
     pcmk__xml_free(action->xml);
     free(action);
 }
 
 /*!
  * \internal
  * \brief Free a transition graph synapse object
  *
  * \param[in,out] user_data  Synapse to free
  */
 static void
 free_graph_synapse(gpointer user_data)
 {
     pcmk__graph_synapse_t *synapse = user_data;
 
     g_list_free_full(synapse->actions, free_graph_action);
     g_list_free_full(synapse->inputs, free_graph_action);
     free(synapse);
 }
 
 /*!
  * \internal
  * \brief Free a transition graph object
  *
  * \param[in,out] graph  Transition graph to free
  */
 void
 pcmk__free_graph(pcmk__graph_t *graph)
 {
     if (graph != NULL) {
         g_list_free_full(graph->synapses, free_graph_synapse);
         free(graph->source);
         free(graph->failed_stop_offset);
         free(graph->failed_start_offset);
         free(graph);
     }
 }
 
 
 /*
  * Functions for updating graph
  */
 
 /*!
  * \internal
  * \brief Update synapse after completed prerequisite
  *
  * A synapse is ready to be executed once all its prerequisite actions (inputs)
  * complete. Given a completed action, check whether it is an input for a given
  * synapse, and if so, mark the input as confirmed, and mark the synapse as
  * ready if appropriate.
  *
  * \param[in,out] synapse    Transition graph synapse to update
  * \param[in]     action_id  ID of an action that completed
  *
  * \note The only substantial effect here is confirming synapse inputs.
  *       should_fire_synapse() will recalculate pcmk__synapse_ready, so the only
  *       thing that uses the pcmk__synapse_ready from here is
  *       synapse_state_str().
  */
 static void
 update_synapse_ready(pcmk__graph_synapse_t *synapse, int action_id)
 {
     if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) {
         return; // All inputs have already been confirmed
     }
 
     // Presume ready until proven otherwise
     pcmk__set_synapse_flags(synapse, pcmk__synapse_ready);
 
     for (GList *lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_action_t *prereq = (pcmk__graph_action_t *) lpc->data;
 
         if (prereq->id == action_id) {
             crm_trace("Confirming input %d of synapse %d",
                       action_id, synapse->id);
             pcmk__set_graph_action_flags(prereq, pcmk__graph_action_confirmed);
 
         } else if (!pcmk_is_set(prereq->flags, pcmk__graph_action_confirmed)) {
             pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
             crm_trace("Synapse %d still not ready after action %d",
                       synapse->id, action_id);
         }
     }
     if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) {
         crm_trace("Synapse %d is now ready to execute", synapse->id);
     }
 }
 
 /*!
  * \internal
  * \brief Update action and synapse confirmation after action completion
  *
  * \param[in,out] synapse    Transition graph synapse that action belongs to
  * \param[in]     action_id  ID of action that completed
  */
 static void
 update_synapse_confirmed(pcmk__graph_synapse_t *synapse, int action_id)
 {
     bool all_confirmed = true;
 
     for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_action_t *action = (pcmk__graph_action_t *) lpc->data;
 
         if (action->id == action_id) {
             crm_trace("Confirmed action %d of synapse %d",
                       action_id, synapse->id);
             pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
 
         } else if (all_confirmed &&
                    !pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) {
             all_confirmed = false;
             crm_trace("Synapse %d still not confirmed after action %d",
                       synapse->id, action_id);
         }
     }
 
     if (all_confirmed
         && !pcmk_is_set(synapse->flags, pcmk__synapse_confirmed)) {
         crm_trace("Confirmed synapse %d", synapse->id);
         pcmk__set_synapse_flags(synapse, pcmk__synapse_confirmed);
     }
 }
 
 /*!
  * \internal
  * \brief Update the transition graph with a completed action result
  *
  * \param[in,out] graph   Transition graph to update
  * \param[in]     action  Action that completed
  */
 void
 pcmk__update_graph(pcmk__graph_t *graph, const pcmk__graph_action_t *action)
 {
     for (GList *lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
 
         if (pcmk_any_flags_set(synapse->flags,
                                pcmk__synapse_confirmed|pcmk__synapse_failed)) {
             continue; // This synapse already completed
 
         } else if (pcmk_is_set(synapse->flags, pcmk__synapse_executed)) {
             update_synapse_confirmed(synapse, action->id);
 
         } else if (!pcmk_is_set(action->flags, pcmk__graph_action_failed)
                    || (synapse->priority == PCMK_SCORE_INFINITY)) {
             update_synapse_ready(synapse, action->id);
         }
     }
 }
 
 
 /*
  * Functions for executing graph
  */
 
 /* A transition graph consists of various types of actions. The library caller
  * registers execution functions for each action type, which will be stored
  * here.
  */
 static pcmk__graph_functions_t *graph_fns = NULL;
 
 /*!
  * \internal
  * \brief Set transition graph execution functions
  *
  * \param[in]  Execution functions to use
  */
 void
 pcmk__set_graph_functions(pcmk__graph_functions_t *fns)
 {
 
     pcmk__assert((fns != NULL) && (fns->rsc != NULL) && (fns->cluster != NULL)
                  && (fns->pseudo != NULL) && (fns->fence != NULL));
     crm_debug("Setting custom functions for executing transition graphs");
     graph_fns = fns;
 }
 
 /*!
  * \internal
  * \brief Check whether a graph synapse is ready to be executed
  *
  * \param[in,out] graph    Transition graph that synapse is part of
  * \param[in,out] synapse  Synapse to check
  *
  * \return true if synapse is ready, false otherwise
  */
 static bool
 should_fire_synapse(pcmk__graph_t *graph, pcmk__graph_synapse_t *synapse)
 {
     GList *lpc = NULL;
 
     pcmk__set_synapse_flags(synapse, pcmk__synapse_ready);
     for (lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_action_t *prereq = (pcmk__graph_action_t *) lpc->data;
 
         if (!(pcmk_is_set(prereq->flags, pcmk__graph_action_confirmed))) {
             crm_trace("Input %d for synapse %d not yet confirmed",
                       prereq->id, synapse->id);
             pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
             break;
 
         } else if (pcmk_is_set(prereq->flags, pcmk__graph_action_failed)) {
             crm_trace("Input %d for synapse %d confirmed but failed",
                       prereq->id, synapse->id);
             pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
             break;
         }
     }
     if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) {
         crm_trace("Synapse %d is ready to execute", synapse->id);
     } else {
         return false;
     }
 
     for (lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_action_t *a = (pcmk__graph_action_t *) lpc->data;
 
         if (a->type == pcmk__pseudo_graph_action) {
             /* None of the below applies to pseudo ops */
 
         } else if (synapse->priority < graph->abort_priority) {
             crm_trace("Skipping synapse %d: priority %d is less than "
                       "abort priority %d",
                       synapse->id, synapse->priority, graph->abort_priority);
             graph->skipped++;
             return false;
 
         } else if (graph_fns->allowed && !(graph_fns->allowed(graph, a))) {
             crm_trace("Deferring synapse %d: not allowed", synapse->id);
             return false;
         }
     }
 
     return true;
 }
 
 /*!
  * \internal
  * \brief Initiate an action from a transition graph
  *
  * \param[in,out] graph   Transition graph containing action
  * \param[in,out] action  Action to execute
  *
  * \return Standard Pacemaker return code
  */
 static int
 initiate_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     const char *id = pcmk__xe_id(action->xml);
 
     CRM_CHECK(id != NULL, return EINVAL);
     CRM_CHECK(!pcmk_is_set(action->flags, pcmk__graph_action_executed),
               return pcmk_rc_already);
 
     pcmk__set_graph_action_flags(action, pcmk__graph_action_executed);
     switch (action->type) {
         case pcmk__pseudo_graph_action:
             crm_trace("Executing pseudo-action %d (%s)", action->id, id);
             return graph_fns->pseudo(graph, action);
 
         case pcmk__rsc_graph_action:
             crm_trace("Executing resource action %d (%s)", action->id, id);
             return graph_fns->rsc(graph, action);
 
         case pcmk__cluster_graph_action:
             if (pcmk__str_eq(crm_element_value(action->xml, PCMK_XA_OPERATION),
                              PCMK_ACTION_STONITH, pcmk__str_none)) {
                 crm_trace("Executing fencing action %d (%s)",
                           action->id, id);
                 return graph_fns->fence(graph, action);
             }
             crm_trace("Executing cluster action %d (%s)", action->id, id);
             return graph_fns->cluster(graph, action);
 
         default:
             crm_err("Unsupported graph action type <%s " PCMK_XA_ID "='%s'> "
                     "(bug?)",
                     action->xml->name, id);
             return EINVAL;
     }
 }
 
 /*!
  * \internal
  * \brief Execute a graph synapse
  *
  * \param[in,out] graph    Transition graph with synapse to execute
  * \param[in,out] synapse  Synapse to execute
  *
  * \return Standard Pacemaker return value
  */
 static int
 fire_synapse(pcmk__graph_t *graph, pcmk__graph_synapse_t *synapse)
 {
     pcmk__set_synapse_flags(synapse, pcmk__synapse_executed);
     for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_action_t *action = (pcmk__graph_action_t *) lpc->data;
         int rc = initiate_action(graph, action);
 
         if (rc != pcmk_rc_ok) {
             crm_err("Failed initiating <%s " PCMK_XA_ID "=%d> in synapse %d: "
                     "%s",
                     action->xml->name, action->id, synapse->id,
                     pcmk_rc_str(rc));
             pcmk__set_synapse_flags(synapse, pcmk__synapse_confirmed);
             pcmk__set_graph_action_flags(action,
                                          pcmk__graph_action_confirmed
                                          |pcmk__graph_action_failed);
             return pcmk_rc_error;
         }
     }
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Dummy graph method that can be used with simulations
  *
  * \param[in,out] graph   Transition graph containing action
  * \param[in,out] action  Graph action to be initiated
  *
  * \return Standard Pacemaker return code
  * \note If the PE_fail environment variable is set to the action ID,
  *       then the graph action will be marked as failed.
  */
 static int
 pseudo_action_dummy(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     static int fail = -1;
 
     if (fail < 0) {
         long long fail_ll;
 
         if ((pcmk__scan_ll(getenv("PE_fail"), &fail_ll, 0LL) == pcmk_rc_ok)
             && (fail_ll > 0LL) && (fail_ll <= INT_MAX)) {
             fail = (int) fail_ll;
         } else {
             fail = 0;
         }
     }
 
     if (action->id == fail) {
         crm_err("Dummy event handler: pretending action %d failed", action->id);
         pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
         graph->abort_priority = PCMK_SCORE_INFINITY;
     } else {
         crm_trace("Dummy event handler: action %d initiated", action->id);
     }
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     pcmk__update_graph(graph, action);
     return pcmk_rc_ok;
 }
 
 static pcmk__graph_functions_t default_fns = {
     pseudo_action_dummy,
     pseudo_action_dummy,
     pseudo_action_dummy,
     pseudo_action_dummy
 };
 
 /*!
  * \internal
  * \brief Execute all actions in a transition graph
  *
  * \param[in,out] graph  Transition graph to execute
  *
  * \return Status of transition after execution
  */
 enum pcmk__graph_status
 pcmk__execute_graph(pcmk__graph_t *graph)
 {
     GList *lpc = NULL;
     int log_level = LOG_DEBUG;
     enum pcmk__graph_status pass_result = pcmk__graph_active;
     const char *status = "In progress";
 
     if (graph_fns == NULL) {
         graph_fns = &default_fns;
     }
     if (graph == NULL) {
         return pcmk__graph_complete;
     }
 
     graph->fired = 0;
     graph->pending = 0;
     graph->skipped = 0;
     graph->completed = 0;
     graph->incomplete = 0;
 
     // Count completed and in-flight synapses
     for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
 
         if (pcmk_is_set(synapse->flags, pcmk__synapse_confirmed)) {
             graph->completed++;
 
         } else if (!pcmk_is_set(synapse->flags, pcmk__synapse_failed)
                    && pcmk_is_set(synapse->flags, pcmk__synapse_executed)) {
             graph->pending++;
         }
     }
     crm_trace("Executing graph %d (%d synapses already completed, %d pending)",
               graph->id, graph->completed, graph->pending);
 
     // Execute any synapses that are ready
     for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
 
         if ((graph->batch_limit > 0)
             && (graph->pending >= graph->batch_limit)) {
 
             crm_debug("Throttling graph execution: batch limit (%d) reached",
                       graph->batch_limit);
             break;
 
         } else if (pcmk_is_set(synapse->flags, pcmk__synapse_failed)) {
             graph->skipped++;
             continue;
 
         } else if (pcmk_any_flags_set(synapse->flags,
                                       pcmk__synapse_confirmed
                                       |pcmk__synapse_executed)) {
             continue; // Already handled
 
         } else if (should_fire_synapse(graph, synapse)) {
             graph->fired++;
             if (fire_synapse(graph, synapse) != pcmk_rc_ok) {
                 crm_err("Synapse %d failed to fire", synapse->id);
                 log_level = LOG_ERR;
                 graph->abort_priority = PCMK_SCORE_INFINITY;
                 graph->incomplete++;
                 graph->fired--;
             }
 
             if (!(pcmk_is_set(synapse->flags, pcmk__synapse_confirmed))) {
                 graph->pending++;
             }
 
         } else {
             crm_trace("Synapse %d cannot fire", synapse->id);
             graph->incomplete++;
         }
     }
 
     if ((graph->pending == 0) && (graph->fired == 0)) {
         graph->complete = true;
 
         if ((graph->incomplete != 0) && (graph->abort_priority <= 0)) {
             log_level = LOG_WARNING;
             pass_result = pcmk__graph_terminated;
             status = "Terminated";
 
         } else if (graph->skipped != 0) {
             log_level = LOG_NOTICE;
             pass_result = pcmk__graph_complete;
             status = "Stopped";
 
         } else {
             log_level = LOG_NOTICE;
             pass_result = pcmk__graph_complete;
             status = "Complete";
         }
 
     } else if (graph->fired == 0) {
         pass_result = pcmk__graph_pending;
     }
 
     do_crm_log(log_level,
                "Transition %d (Complete=%d, Pending=%d,"
                " Fired=%d, Skipped=%d, Incomplete=%d, Source=%s): %s",
                graph->id, graph->completed, graph->pending, graph->fired,
                graph->skipped, graph->incomplete, graph->source, status);
 
     return pass_result;
 }
 
 
 /*
  * Functions for unpacking transition graph XML into structs
  */
 
 /*!
  * \internal
  * \brief Unpack a transition graph action from XML
  *
  * \param[in] parent      Synapse that action is part of
  * \param[in] xml_action  Action XML to unparse
  *
  * \return Newly allocated action on success, or NULL otherwise
  */
 static pcmk__graph_action_t *
 unpack_action(pcmk__graph_synapse_t *parent, xmlNode *xml_action)
 {
     enum pcmk__graph_action_type action_type;
     pcmk__graph_action_t *action = NULL;
     const char *value = pcmk__xe_id(xml_action);
 
     if (value == NULL) {
         crm_err("Ignoring transition graph action without " PCMK_XA_ID
                 " (bug?)");
         crm_log_xml_trace(xml_action, "invalid");
         return NULL;
     }
 
     if (pcmk__xe_is(xml_action, PCMK__XE_RSC_OP)) {
         action_type = pcmk__rsc_graph_action;
 
     } else if (pcmk__xe_is(xml_action, PCMK__XE_PSEUDO_EVENT)) {
         action_type = pcmk__pseudo_graph_action;
 
     } else if (pcmk__xe_is(xml_action, PCMK__XE_CRM_EVENT)) {
         action_type = pcmk__cluster_graph_action;
 
     } else {
         crm_err("Ignoring transition graph action of unknown type '%s' (bug?)",
                 xml_action->name);
         crm_log_xml_trace(xml_action, "invalid");
         return NULL;
     }
 
     action = calloc(1, sizeof(pcmk__graph_action_t));
     if (action == NULL) {
         crm_perror(LOG_CRIT, "Cannot unpack transition graph action");
         crm_log_xml_trace(xml_action, "lost");
         return NULL;
     }
 
     pcmk__scan_min_int(value, &(action->id), -1);
     action->type = pcmk__rsc_graph_action;
     action->xml = pcmk__xml_copy(NULL, xml_action);
     action->synapse = parent;
     action->type = action_type;
     action->params = xml2list(action->xml);
 
     value = crm_meta_value(action->params, PCMK_META_TIMEOUT);
     pcmk__scan_min_int(value, &(action->timeout), 0);
 
     /* Take PCMK_META_START_DELAY into account for the timeout of the action
      * timer
      */
     value = crm_meta_value(action->params, PCMK_META_START_DELAY);
     {
         int start_delay;
 
         pcmk__scan_min_int(value, &start_delay, 0);
         action->timeout += start_delay;
     }
 
     if (pcmk__guint_from_hash(action->params, CRM_META "_" PCMK_META_INTERVAL,
                               0, &(action->interval_ms)) != pcmk_rc_ok) {
         action->interval_ms = 0;
     }
 
     crm_trace("Action %d has timer set to %dms", action->id, action->timeout);
 
     return action;
 }
 
 /*!
  * \internal
  * \brief Unpack transition graph synapse from XML
  *
  * \param[in,out] new_graph    Transition graph that synapse is part of
  * \param[in]     xml_synapse  Synapse XML
  *
  * \return Newly allocated synapse on success, or NULL otherwise
  */
 static pcmk__graph_synapse_t *
 unpack_synapse(pcmk__graph_t *new_graph, const xmlNode *xml_synapse)
 {
     const char *value = NULL;
     xmlNode *action_set = NULL;
     pcmk__graph_synapse_t *new_synapse = NULL;
 
     crm_trace("Unpacking synapse %s", pcmk__xe_id(xml_synapse));
 
     new_synapse = calloc(1, sizeof(pcmk__graph_synapse_t));
     if (new_synapse == NULL) {
         return NULL;
     }
 
     pcmk__scan_min_int(pcmk__xe_id(xml_synapse), &(new_synapse->id), 0);
 
     value = crm_element_value(xml_synapse, PCMK__XA_PRIORITY);
     pcmk__scan_min_int(value, &(new_synapse->priority), 0);
 
     CRM_CHECK(new_synapse->id >= 0,
               free_graph_synapse((gpointer) new_synapse); return NULL);
 
     new_graph->num_synapses++;
 
     crm_trace("Unpacking synapse %s action sets",
               crm_element_value(xml_synapse, PCMK_XA_ID));
 
     for (action_set = pcmk__xe_first_child(xml_synapse, PCMK__XE_ACTION_SET,
                                            NULL, NULL);
          action_set != NULL;
          action_set = pcmk__xe_next(action_set, PCMK__XE_ACTION_SET)) {
 
         for (xmlNode *action = pcmk__xe_first_child(action_set, NULL, NULL,
                                                     NULL);
              action != NULL; action = pcmk__xe_next(action, NULL)) {
 
             pcmk__graph_action_t *new_action = unpack_action(new_synapse,
                                                              action);
 
             if (new_action == NULL) {
                 continue;
             }
 
             crm_trace("Adding action %d to synapse %d",
                       new_action->id, new_synapse->id);
             new_graph->num_actions++;
             new_synapse->actions = g_list_append(new_synapse->actions,
                                                  new_action);
         }
     }
 
     crm_trace("Unpacking synapse %s inputs", pcmk__xe_id(xml_synapse));
 
     for (xmlNode *inputs = pcmk__xe_first_child(xml_synapse, PCMK__XE_INPUTS,
                                                 NULL, NULL);
          inputs != NULL; inputs = pcmk__xe_next(inputs, PCMK__XE_INPUTS)) {
 
         for (xmlNode *trigger = pcmk__xe_first_child(inputs, PCMK__XE_TRIGGER,
                                                      NULL, NULL);
              trigger != NULL;
              trigger = pcmk__xe_next(trigger, PCMK__XE_TRIGGER)) {
 
             for (xmlNode *input = pcmk__xe_first_child(trigger, NULL, NULL,
                                                        NULL);
                  input != NULL; input = pcmk__xe_next(input, NULL)) {
 
                 pcmk__graph_action_t *new_input = unpack_action(new_synapse,
                                                                 input);
 
                 if (new_input == NULL) {
                     continue;
                 }
 
                 crm_trace("Adding input %d to synapse %d",
                            new_input->id, new_synapse->id);
 
                 new_synapse->inputs = g_list_append(new_synapse->inputs,
                                                     new_input);
             }
         }
     }
 
     return new_synapse;
 }
 
 /*!
  * \internal
  * \brief Unpack transition graph XML
  *
  * \param[in] xml_graph  Transition graph XML to unpack
  * \param[in] reference  Where the XML came from (for logging)
  *
  * \return Newly allocated transition graph on success, NULL otherwise
  * \note The caller is responsible for freeing the return value using
  *       pcmk__free_graph().
  * \note The XML is expected to be structured like:
          <transition_graph ...>
            <synapse id="0">
              <action_set>
                <rsc_op id="2" ...>
                ...
              </action_set>
              <inputs>
                  <rsc_op id="1" ...
                  ...
              </inputs>
            </synapse>
            ...
          </transition_graph>
  */
 pcmk__graph_t *
 pcmk__unpack_graph(const xmlNode *xml_graph, const char *reference)
 {
     pcmk__graph_t *new_graph = NULL;
 
     new_graph = calloc(1, sizeof(pcmk__graph_t));
     if (new_graph == NULL) {
         return NULL;
     }
 
     new_graph->source = strdup(pcmk__s(reference, "unknown"));
     if (new_graph->source == NULL) {
         pcmk__free_graph(new_graph);
         return NULL;
     }
 
     new_graph->completion_action = pcmk__graph_done;
 
     // Parse top-level attributes from PCMK__XE_TRANSITION_GRAPH
     if (xml_graph != NULL) {
         const char *buf = crm_element_value(xml_graph, "transition_id");
 
         CRM_CHECK(buf != NULL,
                   pcmk__free_graph(new_graph); return NULL);
         pcmk__scan_min_int(buf, &(new_graph->id), 1);
 
         buf = crm_element_value(xml_graph, PCMK_OPT_CLUSTER_DELAY);
         CRM_CHECK(buf != NULL,
                   pcmk__free_graph(new_graph); return NULL);
         pcmk_parse_interval_spec(buf, &(new_graph->network_delay));
 
         buf = crm_element_value(xml_graph, PCMK_OPT_STONITH_TIMEOUT);
         if (buf == NULL) {
             new_graph->stonith_timeout = new_graph->network_delay;
         } else {
             pcmk_parse_interval_spec(buf, &(new_graph->stonith_timeout));
         }
 
         // Use 0 (dynamic limit) as default/invalid, -1 (no limit) as minimum
         buf = crm_element_value(xml_graph, PCMK_OPT_BATCH_LIMIT);
         if ((buf == NULL)
             || (pcmk__scan_min_int(buf, &(new_graph->batch_limit),
                                    -1) != pcmk_rc_ok)) {
             new_graph->batch_limit = 0;
         }
 
         buf = crm_element_value(xml_graph, PCMK_OPT_MIGRATION_LIMIT);
         pcmk__scan_min_int(buf, &(new_graph->migration_limit), -1);
 
         new_graph->failed_stop_offset =
             crm_element_value_copy(xml_graph, "failed-stop-offset");
         new_graph->failed_start_offset =
             crm_element_value_copy(xml_graph, "failed-start-offset");
 
         if (crm_element_value_epoch(xml_graph, "recheck-by",
                                     &(new_graph->recheck_by)) != pcmk_ok) {
             new_graph->recheck_by = 0;
         }
     }
 
     // Unpack each child <synapse> element
     for (const xmlNode *synapse_xml = pcmk__xe_first_child(xml_graph,
-                                                           "synapse", NULL,
-                                                           NULL);
+                                                           PCMK__XE_SYNAPSE,
+                                                           NULL, NULL);
          synapse_xml != NULL;
-         synapse_xml = pcmk__xe_next(synapse_xml, "synapse")) {
+         synapse_xml = pcmk__xe_next(synapse_xml, PCMK__XE_SYNAPSE)) {
 
         pcmk__graph_synapse_t *new_synapse = unpack_synapse(new_graph,
                                                             synapse_xml);
 
         if (new_synapse != NULL) {
             new_graph->synapses = g_list_append(new_graph->synapses,
                                                 new_synapse);
         }
     }
 
     crm_debug("Unpacked transition %d from %s: %d actions in %d synapses",
               new_graph->id, new_graph->source, new_graph->num_actions,
               new_graph->num_synapses);
 
     return new_graph;
 }
 
 
 /*
  * Other transition graph utilities
  */
 
 /*!
  * \internal
  * \brief Synthesize an executor event from a graph action
  *
  * \param[in] resource     If not NULL, use greater call ID than in this XML
  * \param[in] action       Graph action
  * \param[in] status       What to use as event execution status
  * \param[in] rc           What to use as event exit status
  * \param[in] exit_reason  What to use as event exit reason
  *
  * \return Newly allocated executor event on success, or NULL otherwise
  */
 lrmd_event_data_t *
 pcmk__event_from_graph_action(const xmlNode *resource,
                               const pcmk__graph_action_t *action,
                               int status, int rc, const char *exit_reason)
 {
     lrmd_event_data_t *op = NULL;
     GHashTableIter iter;
     const char *name = NULL;
     const char *value = NULL;
     xmlNode *action_resource = NULL;
 
     CRM_CHECK(action != NULL, return NULL);
     CRM_CHECK(action->type == pcmk__rsc_graph_action, return NULL);
 
     action_resource = pcmk__xe_first_child(action->xml, PCMK_XE_PRIMITIVE, NULL,
                                            NULL);
     CRM_CHECK(action_resource != NULL, crm_log_xml_warn(action->xml, "invalid");
                                        return NULL);
 
     op = lrmd_new_event(pcmk__xe_id(action_resource),
                         crm_element_value(action->xml, PCMK_XA_OPERATION),
                         action->interval_ms);
     lrmd__set_result(op, rc, status, exit_reason);
     op->t_run = time(NULL);
     op->t_rcchange = op->t_run;
     op->params = pcmk__strkey_table(free, free);
 
     g_hash_table_iter_init(&iter, action->params);
     while (g_hash_table_iter_next(&iter, (void **)&name, (void **)&value)) {
         pcmk__insert_dup(op->params, name, value);
     }
 
     for (xmlNode *xop = pcmk__xe_first_child(resource, NULL, NULL, NULL);
          xop != NULL; xop = pcmk__xe_next(xop, NULL)) {
 
         int tmp = 0;
 
         crm_element_value_int(xop, PCMK__XA_CALL_ID, &tmp);
         crm_debug("Got call_id=%d for %s", tmp, pcmk__xe_id(resource));
         if (tmp > op->call_id) {
             op->call_id = tmp;
         }
     }
 
     op->call_id++;
     return op;
 }
diff --git a/lib/pacemaker/pcmk_graph_producer.c b/lib/pacemaker/pcmk_graph_producer.c
index d8680491cc..352001c753 100644
--- a/lib/pacemaker/pcmk_graph_producer.c
+++ b/lib/pacemaker/pcmk_graph_producer.c
@@ -1,1105 +1,1105 @@
 /*
  * Copyright 2004-2024 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/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->priv->name)
 
 /*!
  * \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 = pcmk__xe_create(xml, PCMK_XE_NODE);
     crm_xml_add(node_xml, PCMK_XA_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 pcmk_node_t *node, void *xml)
 {
     add_node_to_xml_by_id(node->priv->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]     scheduler  Scheduler data
  *
  * \return Count of nodes added
  * \note Only Pacemaker Remote nodes are considered currently
  */
 static int
 add_maintenance_nodes(xmlNode *xml, const pcmk_scheduler_t *scheduler)
 {
     xmlNode *maintenance = NULL;
     int count = 0;
 
     if (xml != NULL) {
         maintenance = pcmk__xe_create(xml, PCMK__XE_MAINTENANCE);
     }
     for (const GList *iter = scheduler->nodes;
          iter != NULL; iter = iter->next) {
         const pcmk_node_t *node = iter->data;
 
         if (!pcmk__is_pacemaker_remote_node(node)) {
             continue;
         }
         if ((node->details->maintenance
              && !pcmk_is_set(node->priv->flags, pcmk__node_remote_maint))
             || (!node->details->maintenance
                 && pcmk_is_set(node->priv->flags, pcmk__node_remote_maint))) {
 
             if (maintenance != NULL) {
                 crm_xml_add(add_node_to_xml_by_id(node->priv->id,
                                                   maintenance),
                             PCMK__XA_NODE_IN_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] scheduler  Scheduler data
  */
 static void
 add_maintenance_update(pcmk_scheduler_t *scheduler)
 {
     pcmk_action_t *action = NULL;
 
     if (add_maintenance_nodes(NULL, scheduler) != 0) {
         action = get_pseudo_op(PCMK_ACTION_MAINTENANCE_NODES, scheduler);
         pcmk__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 pcmk_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 = pcmk__xe_create(xml, PCMK__XE_DOWNED);
         add_node_to_xml_by_id(action->node->priv->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,
                                                 PCMK__META_STONITH_ACTION);
 
         if (pcmk__is_fencing_action(fence)) {
             xmlNode *downed = pcmk__xe_create(xml, PCMK__XE_DOWNED);
             add_node_to_xml_by_id(action->node->priv->id, downed);
             pe_foreach_guest_node(action->node->priv->scheduler,
                                   action->node, add_node_to_xml, downed);
         }
 
     } else if ((action->rsc != NULL)
                && pcmk_is_set(action->rsc->flags,
                               pcmk__rsc_is_remote_connection)
                && 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;
         pcmk_action_t *input;
         bool migrating = false;
 
         for (iter = action->actions_before; iter != NULL; iter = iter->next) {
             input = ((pcmk__related_action_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 = pcmk__xe_create(xml, PCMK__XE_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 pcmk_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");
 
         return pcmk__notify_key(action->rsc->priv->history_id, n_type,
                                 n_task);
     }
     return pcmk__op_key(action->rsc->priv->history_id,
                         pcmk__s(action->cancel_task, 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 pcmk_action_t *action, xmlNode *xml)
 {
     pcmk_node_t *router_node = pcmk__connection_host_for_action(action);
 
     crm_xml_add(xml, PCMK__META_ON_NODE, action->node->priv->name);
     crm_xml_add(xml, PCMK__META_ON_NODE_UUID, action->node->priv->id);
     if (router_node != NULL) {
         crm_xml_add(xml, PCMK__XA_ROUTER_NODE, router_node->priv->name);
     }
 }
 
 /*!
  * \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 pcmk_action_t *action, xmlNode *action_xml)
 {
     xmlNode *rsc_xml = NULL;
     const char *attr_list[] = {
         PCMK_XA_CLASS,
         PCMK_XA_PROVIDER,
         PCMK_XA_TYPE,
     };
 
     /* If a resource is locked to a node via PCMK_OPT_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, PCMK_OPT_SHUTDOWN_LOCK,
                        (long long) action->rsc->priv->lock_time);
     }
 
     // List affected resource
 
     rsc_xml = pcmk__xe_create(action_xml,
                               (const char *) action->rsc->priv->xml->name);
     if (pcmk_is_set(action->rsc->flags, pcmk__rsc_removed)
         && (action->rsc->priv->history_id != 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 history ID %s",
                   action->rsc->id, action->rsc->priv->history_id);
         crm_xml_add(rsc_xml, PCMK_XA_ID, action->rsc->priv->history_id);
         crm_xml_add(rsc_xml, PCMK__XA_LONG_ID, action->rsc->id);
 
     } else if (!pcmk_is_set(action->rsc->flags, pcmk__rsc_unique)) {
         const char *xml_id = pcmk__xe_id(action->rsc->priv->xml);
 
         crm_debug("Using anonymous clone name %s for %s (aka %s)",
                   xml_id, action->rsc->id, action->rsc->priv->history_id);
 
         /* ID is what we'd like client to use
          * LONG_ID is what they might know it as instead
          *
          * LONG_ID 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, PCMK_XA_ID, xml_id);
         if ((action->rsc->priv->history_id != NULL)
             && !pcmk__str_eq(xml_id, action->rsc->priv->history_id,
                              pcmk__str_none)) {
             crm_xml_add(rsc_xml, PCMK__XA_LONG_ID,
                         action->rsc->priv->history_id);
         } else {
             crm_xml_add(rsc_xml, PCMK__XA_LONG_ID, action->rsc->id);
         }
 
     } else {
         pcmk__assert(action->rsc->priv->history_id == NULL);
         crm_xml_add(rsc_xml, PCMK_XA_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->priv->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(pcmk_action_t *action, xmlNode *action_xml)
 {
     xmlNode *args_xml = NULL;
     pcmk_resource_t *rsc = action->rsc;
 
     /* 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 = pcmk__xe_create(action_xml, PCMK__XE_ATTRIBUTES);
 
     crm_xml_add(args_xml, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
     g_hash_table_foreach(action->extra, hash2field, args_xml);
 
     if ((rsc != NULL) && (action->node != NULL)) {
         // Get the resource instance attributes, evaluated properly for node
         GHashTable *params = pe_rsc_params(rsc, action->node,
                                            rsc->priv->scheduler);
 
         pcmk__substitute_remote_addr(rsc, params);
 
         g_hash_table_foreach(params, hash2smartfield, args_xml);
 
     } else if ((rsc != NULL)
                && (rsc->priv->variant <= pcmk__rsc_variant_primitive)) {
         GHashTable *params = pe_rsc_params(rsc, NULL, rsc->priv->scheduler);
 
         g_hash_table_foreach(params, hash2smartfield, args_xml);
     }
 
     g_hash_table_foreach(action->meta, hash2metafield, args_xml);
     if (rsc != NULL) {
         pcmk_resource_t *parent = rsc;
 
         while (parent != NULL) {
             parent->priv->cmds->add_graph_meta(parent, args_xml);
             parent = parent->priv->parent;
         }
 
         pcmk__add_guest_meta_to_xml(args_xml, action);
     }
 
     pcmk__xe_sort_attrs(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]     scheduler     Scheduler data
  */
 static void
 create_graph_action(xmlNode *parent, pcmk_action_t *action, bool skip_details,
                     const pcmk_scheduler_t *scheduler)
 {
     bool needs_node_info = true;
     bool needs_maintenance_info = false;
     xmlNode *action_xml = NULL;
 
     if ((action == NULL) || (scheduler == 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 = pcmk__xe_create(parent, PCMK__XE_PSEUDO_EVENT);
         } else {
             action_xml = pcmk__xe_create(parent, PCMK__XE_CRM_EVENT);
         }
 
     } else if (pcmk__str_any_of(action->task,
                                 PCMK_ACTION_DO_SHUTDOWN,
                                 PCMK_ACTION_CLEAR_FAILCOUNT, NULL)) {
         action_xml = pcmk__xe_create(parent, PCMK__XE_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 = pcmk__xe_create(parent, PCMK__XE_CRM_EVENT);
         crm_xml_add(action_xml, PCMK__XA_MODE, PCMK__VALUE_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 = pcmk__xe_create(parent, PCMK__XE_PSEUDO_EVENT);
         needs_node_info = false;
 
     } else {
         action_xml = pcmk__xe_create(parent, PCMK__XE_RSC_OP);
     }
 
     crm_xml_add_int(action_xml, PCMK_XA_ID, action->id);
     crm_xml_add(action_xml, PCMK_XA_OPERATION, action->task);
 
     if ((action->rsc != NULL) && (action->rsc->priv->history_id != NULL)) {
         char *clone_key = NULL;
         guint interval_ms;
 
         if (pcmk__guint_from_hash(action->meta, PCMK_META_INTERVAL, 0,
                                   &interval_ms) != pcmk_rc_ok) {
             interval_ms = 0;
         }
         clone_key = clone_op_key(action, interval_ms);
         crm_xml_add(action_xml, PCMK__XA_OPERATION_KEY, clone_key);
         crm_xml_add(action_xml, "internal_" PCMK__XA_OPERATION_KEY,
                     action->uuid);
         free(clone_key);
     } else {
         crm_xml_add(action_xml, PCMK__XA_OPERATION_KEY, action->uuid);
     }
 
     if (needs_node_info && (action->node != NULL)) {
         add_node_details(action, action_xml);
         pcmk__insert_dup(action->meta, PCMK__META_ON_NODE,
                          action->node->priv->name);
         pcmk__insert_dup(action->meta, PCMK__META_ON_NODE_UUID,
                          action->node->priv->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, scheduler);
     }
 }
 
 /*!
  * \internal
  * \brief Check whether an action should be added to the transition graph
  *
  * \param[in,out] action  Action to check
  *
  * \return true if action should be added to graph, otherwise false
  */
 static bool
 should_add_action_to_graph(pcmk_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, PCMK_META_INTERVAL);
         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) {
         pcmk__sched_err(action->scheduler,
                         "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, pcmk__action_on_dc)) {
         crm_trace("Action %s (%d) should be dumped: "
                   "can run on DC instead of %s",
                   action->uuid, action->id, pcmk__node_name(action->node));
 
     } else if (pcmk__is_guest_or_bundle_node(action->node)
                && !pcmk_is_set(action->node->priv->flags,
                                pcmk__node_remote_reset)) {
         crm_trace("Action %s (%d) should be dumped: "
                   "assuming will be runnable on guest %s",
                   action->uuid, action->id, pcmk__node_name(action->node));
 
     } else if (!action->node->details->online) {
         pcmk__sched_err(action->scheduler,
                         "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) {
         pcmk__sched_err(action->scheduler,
                         "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 pcmk__related_action_t *ordering)
 {
     return pcmk_any_flags_set(ordering->flags,
                               ~(pcmk__ar_then_implies_first_graphed
                                 |pcmk__ar_first_implies_then_graphed
                                 |pcmk__ar_ordered));
 }
 
 /*!
  * \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 pcmk_action_t *action,
                           pcmk__related_action_t *input)
 {
     if (input->graphed) {
         return true;
     }
 
     if (input->flags == pcmk__ar_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->flags, pcmk__ar_min_runnable)) {
         crm_trace("Ignoring %s (%d) input %s (%d): "
                   "minimum number of instances required but input unrunnable",
                   action->uuid, action->id,
                   input->action->uuid, input->action->id);
         return false;
 
     } else if (pcmk_is_set(input->flags, pcmk__ar_unmigratable_then_blocks)
                && !pcmk_is_set(input->action->flags, pcmk__action_runnable)) {
         crm_trace("Ignoring %s (%d) input %s (%d): "
                   "input blocked if 'then' unmigratable",
                   action->uuid, action->id,
                   input->action->uuid, input->action->id);
         return false;
 
     } else if (pcmk_is_set(input->flags, pcmk__ar_if_first_unmigratable)
                && pcmk_is_set(input->action->flags, pcmk__action_migratable)) {
         crm_trace("Ignoring %s (%d) input %s (%d): ordering applies "
                   "only if input is unmigratable, but it is migratable",
                   action->uuid, action->id,
                   input->action->uuid, input->action->id);
         return false;
 
     } else if ((input->flags == pcmk__ar_ordered)
                && 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->flags == pcmk__ar_if_on_same_node_or_target) {
         pcmk_node_t *input_node = input->action->node;
 
         if ((action->rsc != NULL)
             && pcmk__str_eq(action->task, PCMK_ACTION_MIGRATE_TO,
                             pcmk__str_none)) {
 
             pcmk_node_t *assigned = action->rsc->priv->assigned_node;
 
             /* For load_stopped -> migrate_to orderings, we care about where
              * the resource has been assigned, not where migrate_to will be
              * executed.
              */
             if (!pcmk__same_node(input_node, assigned)) {
                 crm_trace("Ignoring %s (%d) input %s (%d): "
                           "migration target %s is not same as input node %s",
                           action->uuid, action->id,
                           input->action->uuid, input->action->id,
                           (assigned? assigned->priv->name : "<none>"),
                           (input_node? input_node->priv->name : "<none>"));
                 input->flags = pcmk__ar_none;
                 return false;
             }
 
         } else if (!pcmk__same_node(input_node, action->node)) {
             crm_trace("Ignoring %s (%d) input %s (%d): "
                       "not on same node (%s vs %s)",
                       action->uuid, action->id,
                       input->action->uuid, input->action->id,
                       (action->node? action->node->priv->name : "<none>"),
                       (input_node? input_node->priv->name : "<none>"));
             input->flags = pcmk__ar_none;
             return false;
 
         } else if (pcmk_is_set(input->action->flags, pcmk__action_optional)) {
             crm_trace("Ignoring %s (%d) input %s (%d): "
                       "ordering optional",
                       action->uuid, action->id,
                       input->action->uuid, input->action->id);
             input->flags = pcmk__ar_none;
             return false;
         }
 
     } else if (input->flags == pcmk__ar_if_required_on_same_node) {
         if (input->action->node && action->node
             && !pcmk__same_node(input->action->node, action->node)) {
             crm_trace("Ignoring %s (%d) input %s (%d): "
                       "not on same node (%s vs %s)",
                       action->uuid, action->id,
                       input->action->uuid, input->action->id,
                       pcmk__node_name(action->node),
                       pcmk__node_name(input->action->node));
             input->flags = pcmk__ar_none;
             return false;
 
         } else if (pcmk_is_set(input->action->flags, pcmk__action_optional)) {
             crm_trace("Ignoring %s (%d) input %s (%d): optional",
                       action->uuid, action->id,
                       input->action->uuid, input->action->id);
             input->flags = pcmk__ar_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")
                && pcmk__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->flags);
     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 pcmk_action_t *init_action,
                      const pcmk_action_t *action, pcmk__related_action_t *input)
 {
     bool has_loop = false;
 
     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->priv->name : "",
                   action->uuid,
                   action->node? action->node->priv->name : "",
                   input->flags);
         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->priv->name : "",
                   init_action->uuid,
                   init_action->node? init_action->node->priv->name : "");
         return true;
     }
 
     pcmk__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->priv->name : "",
               input->action->uuid,
               input->action->node? input->action->node->priv->name : "",
               input->flags,
               init_action->uuid,
               init_action->node? init_action->node->priv->name : "");
 
     // 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,
                                  (pcmk__related_action_t *) iter->data)) {
             // Recursive call already logged a debug message
             has_loop = true;
             break;
         }
     }
 
     pcmk__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->priv->name : "",
                   action->uuid,
                   action->node? action->node->priv->name : "",
                   input->flags);
     }
     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] scheduler  Scheduler data containing graph
  *
  * \return Newly added XML element for new graph synapse
  */
 static xmlNode *
 create_graph_synapse(const pcmk_action_t *action, pcmk_scheduler_t *scheduler)
 {
     int synapse_priority = 0;
-    xmlNode *syn = pcmk__xe_create(scheduler->priv->graph, "synapse");
+    xmlNode *syn = pcmk__xe_create(scheduler->priv->graph, PCMK__XE_SYNAPSE);
 
     crm_xml_add_int(syn, PCMK_XA_ID, scheduler->priv->synapse_count++);
 
     if (action->rsc != NULL) {
         synapse_priority = action->rsc->priv->priority;
     }
     if (action->priority > synapse_priority) {
         synapse_priority = action->priority;
     }
     if (synapse_priority > 0) {
         crm_xml_add_int(syn, PCMK__XA_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  Scheduler data
  *
  * \note This will de-duplicate the action inputs, meaning that the
  *       pcmk__related_action_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)
 {
     pcmk_action_t *action = (pcmk_action_t *) data;
     pcmk_scheduler_t *scheduler = (pcmk_scheduler_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, pcmk__action_inputs_deduplicated)) {
         pcmk__deduplicate_action_inputs(action);
         pcmk__set_action_flags(action, pcmk__action_inputs_deduplicated);
     }
 
     if (pcmk_is_set(action->flags, pcmk__action_added_to_graph)
         || !should_add_action_to_graph(action)) {
         return; // Already added, or shouldn't be
     }
     pcmk__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->priv->name));
 
     syn = create_graph_synapse(action, scheduler);
     set = pcmk__xe_create(syn, PCMK__XE_ACTION_SET);
     in = pcmk__xe_create(syn, PCMK__XE_INPUTS);
 
     create_graph_action(set, action, false, scheduler);
 
     for (GList *lpc = action->actions_before; lpc != NULL; lpc = lpc->next) {
         pcmk__related_action_t *input = lpc->data;
 
         if (should_add_input_to_graph(action, input)) {
             xmlNode *input_xml = pcmk__xe_create(in, PCMK__XE_TRIGGER);
 
             input->graphed = true;
             create_graph_action(input_xml, input->action, true, scheduler);
         }
     }
 }
 
 static int transition_id = 0;
 
 /*!
  * \internal
  * \brief Log a message after calculating a transition
  *
  * \param[in] scheduler  Scheduler data
  * \param[in] filename   Where transition input is stored
  */
 void
 pcmk__log_transition_summary(const pcmk_scheduler_t *scheduler,
                              const char *filename)
 {
     if (pcmk_is_set(scheduler->flags, pcmk__sched_processing_error)
         || pcmk__config_has_error) {
         crm_err("Calculated transition %d (with errors)%s%s",
                 transition_id,
                 (filename == NULL)? "" : ", saving inputs in ",
                 (filename == NULL)? "" : filename);
 
     } else if (pcmk_is_set(scheduler->flags, pcmk__sched_processing_warning)
                || pcmk__config_has_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 (pcmk__config_has_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(pcmk_resource_t *rsc)
 {
     GList *iter = NULL;
 
     pcmk__assert(rsc != NULL);
 
     pcmk__rsc_trace(rsc, "Adding actions for %s to graph", rsc->id);
 
     // First add the resource's own actions
     g_list_foreach(rsc->priv->actions, add_action_to_graph,
                    rsc->priv->scheduler);
 
     // Then recursively add its children's actions (appropriate to variant)
     for (iter = rsc->priv->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *child_rsc = (pcmk_resource_t *) iter->data;
 
         child_rsc->priv->cmds->add_actions_to_graph(child_rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Create a transition graph with all cluster actions needed
  *
  * \param[in,out] scheduler  Scheduler data
  */
 void
 pcmk__create_graph(pcmk_scheduler_t *scheduler)
 {
     GList *iter = NULL;
     const char *value = NULL;
     long long limit = 0LL;
     GHashTable *config_hash = scheduler->priv->options;
     int rc = pcmk_rc_ok;
 
     transition_id++;
     crm_trace("Creating transition graph %d", transition_id);
 
     scheduler->priv->graph = pcmk__xe_create(NULL, PCMK__XE_TRANSITION_GRAPH);
 
     value = pcmk__cluster_option(config_hash, PCMK_OPT_CLUSTER_DELAY);
     crm_xml_add(scheduler->priv->graph, PCMK_OPT_CLUSTER_DELAY, value);
 
     value = pcmk__cluster_option(config_hash, PCMK_OPT_STONITH_TIMEOUT);
     crm_xml_add(scheduler->priv->graph, PCMK_OPT_STONITH_TIMEOUT, value);
 
     crm_xml_add(scheduler->priv->graph, "failed-stop-offset", "INFINITY");
 
     if (pcmk_is_set(scheduler->flags, pcmk__sched_start_failure_fatal)) {
         crm_xml_add(scheduler->priv->graph, "failed-start-offset", "INFINITY");
     } else {
         crm_xml_add(scheduler->priv->graph, "failed-start-offset", "1");
     }
 
     value = pcmk__cluster_option(config_hash, PCMK_OPT_BATCH_LIMIT);
     crm_xml_add(scheduler->priv->graph, PCMK_OPT_BATCH_LIMIT, value);
 
     crm_xml_add_int(scheduler->priv->graph, "transition_id", transition_id);
 
     value = pcmk__cluster_option(config_hash, PCMK_OPT_MIGRATION_LIMIT);
     rc = pcmk__scan_ll(value, &limit, 0LL);
     if (rc != pcmk_rc_ok) {
         crm_warn("Ignoring invalid value '%s' for " PCMK_OPT_MIGRATION_LIMIT
                  ": %s", value, pcmk_rc_str(rc));
     } else if (limit > 0) {
         crm_xml_add(scheduler->priv->graph, PCMK_OPT_MIGRATION_LIMIT, value);
     }
 
     if (scheduler->priv->recheck_by > 0) {
         char *recheck_epoch = NULL;
 
         recheck_epoch = crm_strdup_printf("%llu", (unsigned long long)
                                           scheduler->priv->recheck_by);
         crm_xml_add(scheduler->priv->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 = scheduler->priv->resources; iter != NULL; iter = iter->next) {
         pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
 
         pcmk__rsc_trace(rsc, "Processing actions for %s", rsc->id);
         rsc->priv->cmds->add_actions_to_graph(rsc);
     }
 
     // Add pseudo-action for list of nodes with maintenance state update
     add_maintenance_update(scheduler);
 
     // Add non-resource (node) actions
     for (iter = scheduler->priv->actions; iter != NULL; iter = iter->next) {
         pcmk_action_t *action = (pcmk_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(scheduler->flags, pcmk__sched_quorate)
                 || (scheduler->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",
                          pcmk__node_name(action->node), action->rsc->id,
                          (managed? " blocked" : " unmanaged"),
                          (failed? " failed" : ""), action->uuid);
             }
         }
 
         add_action_to_graph((gpointer) action, (gpointer) scheduler);
     }
 
     crm_log_xml_trace(scheduler->priv->graph, "graph");
 }