diff --git a/lib/pacemaker/libpacemaker_private.h b/lib/pacemaker/libpacemaker_private.h
index de58ec60a6..58435a6217 100644
--- a/lib/pacemaker/libpacemaker_private.h
+++ b/lib/pacemaker/libpacemaker_private.h
@@ -1,1141 +1,1142 @@
 /*
  * Copyright 2021-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__PACEMAKER_LIBPACEMAKER_PRIVATE__H
 #define PCMK__PACEMAKER_LIBPACEMAKER_PRIVATE__H
 
 /* This header is for the sole use of libpacemaker, so that functions can be
  * declared with G_GNUC_INTERNAL for efficiency.
  */
 
 #include <stdio.h>                  // NULL
 #include <stdint.h>                 // uint32_t
 #include <stdbool.h>                // bool, false
 #include <glib.h>                   // guint, gpointer, GList, GHashTable
 #include <libxml/tree.h>            // xmlNode
 
 #include <crm/common/scheduler.h>   // pcmk_action_t, pcmk_node_t, etc.
 #include <crm/common/scheduler_internal.h>  // pcmk__location_t, etc.
 #include <crm/cib.h>                // cib_t
 #include <crm/lrmd_events.h>        // lrmd_event_data_t
 #include <crm/pengine/internal.h>   // pe__const_top_resource(), etc.
 #include <pacemaker.h>              // pcmk_injections_t
 #include <pacemaker-internal.h>     // pcmk__colocation_t
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 // Colocation flags
 enum pcmk__coloc_flags {
     pcmk__coloc_none        = 0U,
 
     // Primary is affected even if already active
     pcmk__coloc_influence   = (1U << 0),
 
     // Colocation was explicitly configured in CIB
     pcmk__coloc_explicit    = (1U << 1),
 };
 
 // Flags to modify the behavior of add_colocated_node_scores()
 enum pcmk__coloc_select {
     // With no other flags, apply all "with this" colocations
     pcmk__coloc_select_default      = 0,
 
     // Apply "this with" colocations instead of "with this" colocations
     pcmk__coloc_select_this_with    = (1 << 0),
 
     // Apply only colocations with non-negative scores
     pcmk__coloc_select_nonnegative  = (1 << 1),
 
     // Apply only colocations with at least one matching node
     pcmk__coloc_select_active       = (1 << 2),
 };
 
 // Flags the update_ordered_actions() method can return
 enum pcmk__updated {
     pcmk__updated_none      = 0,        // Nothing changed
     pcmk__updated_first     = (1 << 0), // First action was updated
     pcmk__updated_then      = (1 << 1), // Then action was updated
 };
 
 #define pcmk__set_updated_flags(au_flags, action, flags_to_set) do {        \
         au_flags = pcmk__set_flags_as(__func__, __LINE__,                   \
                                       LOG_TRACE, "Action update",           \
                                       (action)->uuid, au_flags,             \
                                       (flags_to_set), #flags_to_set);       \
     } while (0)
 
 #define pcmk__clear_updated_flags(au_flags, action, flags_to_clear) do {    \
         au_flags = pcmk__clear_flags_as(__func__, __LINE__,                 \
                                         LOG_TRACE, "Action update",         \
                                         (action)->uuid, au_flags,           \
                                         (flags_to_clear), #flags_to_clear); \
     } while (0)
 
 // Resource assignment methods
 struct pcmk__assignment_methods {
     /*!
      * \internal
      * \brief Assign a resource to a node
      *
      * \param[in,out] rsc           Resource to assign to a node
      * \param[in]     prefer        Node to prefer, if all else is equal
      * \param[in]     stop_if_fail  If \c true and \p rsc can't be assigned to a
      *                              node, set next role to stopped and update
      *                              existing actions (if \p rsc is not a
      *                              primitive, this applies to its primitive
      *                              descendants instead)
      *
      * \return Node that \p rsc is assigned to, if assigned entirely to one node
      *
      * \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource()
      *       can completely undo the assignment. A successful assignment can be
      *       either undone or left alone as final. A failed assignment has the
      *       same effect as calling pcmk__unassign_resource(); there are no side
      *       effects on roles or actions.
      */
     pcmk_node_t *(*assign)(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
                            bool stop_if_fail);
 
     /*!
      * \internal
      * \brief Create all actions needed for a given resource
      *
      * \param[in,out] rsc  Resource to create actions for
      */
     void (*create_actions)(pcmk_resource_t *rsc);
 
     /*!
      * \internal
      * \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 (*create_probe)(pcmk_resource_t *rsc, pcmk_node_t *node);
 
     /*!
      * \internal
      * \brief Create implicit constraints needed for a resource
      *
      * \param[in,out] rsc  Resource to create implicit constraints for
      */
     void (*internal_constraints)(pcmk_resource_t *rsc);
 
     /*!
      * \internal
      * \brief Apply a colocation's score to node scores or resource priority
      *
      * Given a colocation constraint, apply its score to the dependent's
      * allowed node scores (if we are still placing resources) or priority (if
      * we are choosing promotable clone instance roles).
      *
      * \param[in,out] dependent      Dependent resource in colocation
      * \param[in]     primary        Primary resource in colocation
      * \param[in]     colocation     Colocation constraint to apply
      * \param[in]     for_dependent  true if called on behalf of dependent
      *
      * \return The score added to the dependent's priority
      */
     int (*apply_coloc_score)(pcmk_resource_t *dependent,
                              const pcmk_resource_t *primary,
                              const pcmk__colocation_t *colocation,
                              bool for_dependent);
 
     /*!
      * \internal
      * \brief Create list of all resources in colocations with a given resource
      *
      * Given a resource, create a list of all resources involved in mandatory
      * colocations with it, whether directly or via chained colocations.
      *
      * \param[in]     rsc             Resource to add to colocated list
      * \param[in]     orig_rsc        Resource originally requested
      * \param[in,out] colocated_rscs  Existing list
      *
      * \return List of given resource and all resources involved in colocations
      *
      * \note This function is recursive; top-level callers should pass NULL as
      *       \p colocated_rscs and \p orig_rsc, and the desired resource as
      *       \p rsc. The recursive calls will use other values.
      */
     GList *(*colocated_resources)(const pcmk_resource_t *rsc,
                                   const pcmk_resource_t *orig_rsc,
                                   GList *colocated_rscs);
 
     /*!
      * \internal
      * \brief Add colocations affecting a resource as primary to a list
      *
      * Given a resource being assigned (\p orig_rsc) and a resource somewhere in
      * its chain of ancestors (\p rsc, which may be \p orig_rsc), get
      * colocations that affect the ancestor as primary and should affect the
      * resource, and add them to a given list.
      *
      * \param[in]     rsc       Resource whose colocations should be added
      * \param[in]     orig_rsc  Affected resource (\p rsc or a descendant)
      * \param[in,out] list      List of colocations to add to
      *
      * \note All arguments should be non-NULL.
      * \note The pcmk__with_this_colocations() wrapper should usually be used
      *       instead of using this method directly.
      */
     void (*with_this_colocations)(const pcmk_resource_t *rsc,
                                   const pcmk_resource_t *orig_rsc,
                                   GList **list);
 
     /*!
      * \internal
      * \brief Add colocations affecting a resource as dependent to a list
      *
      * Given a resource being assigned (\p orig_rsc) and a resource somewhere in
      * its chain of ancestors (\p rsc, which may be \p orig_rsc), get
      * colocations that affect the ancestor as dependent and should affect the
      * resource, and add them to a given list.
      *
      *
      * \param[in]     rsc       Resource whose colocations should be added
      * \param[in]     orig_rsc  Affected resource (\p rsc or a descendant)
      * \param[in,out] list      List of colocations to add to
      *
      * \note All arguments should be non-NULL.
      * \note The pcmk__this_with_colocations() wrapper should usually be used
      *       instead of using this method directly.
      */
     void (*this_with_colocations)(const pcmk_resource_t *rsc,
                                   const pcmk_resource_t *orig_rsc,
                                   GList **list);
 
     /*!
      * \internal
      * \brief Update nodes with scores of colocated resources' nodes
      *
      * Given a table of nodes and a resource, update the nodes' scores with the
      * scores of the best nodes matching the attribute used for each of the
      * resource's relevant colocations.
      *
      * \param[in,out] source_rsc  Resource whose node scores to add
      * \param[in]     target_rsc  Resource on whose behalf to update \p *nodes
      * \param[in]     log_id      Resource ID for logs (if \c NULL, use
      *                            \p source_rsc ID)
      * \param[in,out] nodes       Nodes to update (set initial contents to
      *                            \c NULL to copy allowed nodes from
      *                            \p source_rsc)
      * \param[in]     colocation  Original colocation constraint (used to get
      *                            configured primary resource's stickiness, and
      *                            to get colocation node attribute; if \c NULL,
      *                            <tt>source_rsc</tt>'s own matching node scores
      *                            will not be added, and \p *nodes must be
      *                            \c NULL as well)
      * \param[in]     factor      Incorporate scores multiplied by this factor
      * \param[in]     flags       Bitmask of enum pcmk__coloc_select values
      *
      * \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation,
      *       and the \c pcmk__coloc_select_this_with flag are used together (and
      *       only by \c cmp_resources()).
      * \note The caller remains responsible for freeing \p *nodes.
      */
     void (*add_colocated_node_scores)(pcmk_resource_t *source_rsc,
                                       const pcmk_resource_t *target_rsc,
                                       const char *log_id, GHashTable **nodes,
                                       const pcmk__colocation_t *colocation,
                                       float factor, uint32_t flags);
 
     /*!
      * \internal
      * \brief Apply a location constraint to a resource's allowed node scores
      *
      * \param[in,out] rsc       Resource to apply constraint to
      * \param[in,out] location  Location constraint to apply
      */
     void (*apply_location)(pcmk_resource_t *rsc, pcmk__location_t *location);
 
     /*!
      * \internal
      * \brief Return action flags for a given resource action
      *
      * \param[in,out] action  Action to get flags for
      * \param[in]     node    If not NULL, limit effects to this node
      *
      * \return Flags appropriate to \p action on \p node
      * \note For primitives, this will be the same as action->flags regardless
      *       of node. For collective resources, the flags can differ due to
      *       multiple instances possibly being involved.
      */
     uint32_t (*action_flags)(pcmk_action_t *action, const pcmk_node_t *node);
 
     /*!
      * \internal
      * \brief Update two actions according to an ordering between them
      *
      * Given information about an ordering of two actions, update the actions'
      * flags (and runnable_before members if appropriate) as appropriate for the
      * ordering. Effects may cascade to other orderings involving the actions as
      * well.
      *
      * \param[in,out] first      'First' action in an ordering
      * \param[in,out] then       'Then' action in an ordering
      * \param[in]     node       If not NULL, limit scope of ordering to this
      *                           node (only used when interleaving instances)
      * \param[in]     flags      Action flags for \p first for ordering purposes
      * \param[in]     filter     Action flags to limit scope of certain updates
      *                           (may include pcmk__action_optional to affect
      *                           only mandatory actions and
      *                           pcmk__action_runnable to affect only runnable
      *                           actions)
      * \param[in]     type       Group of enum pcmk__action_relation_flags
      * \param[in,out] scheduler  Scheduler data
      *
      * \return Group of enum pcmk__updated flags indicating what was updated
      */
     uint32_t (*update_ordered_actions)(pcmk_action_t *first,
                                        pcmk_action_t *then,
                                        const pcmk_node_t *node, uint32_t flags,
                                        uint32_t filter, uint32_t type,
                                        pcmk_scheduler_t *scheduler);
 
     /*!
      * \internal
      * \brief Output a summary of scheduled actions for a resource
      *
      * \param[in,out] rsc  Resource to output actions for
      */
     void (*output_actions)(pcmk_resource_t *rsc);
 
     /*!
      * \internal
      * \brief Add a resource's actions to the transition graph
      *
      * \param[in,out] rsc  Resource whose actions should be added
      */
     void (*add_actions_to_graph)(pcmk_resource_t *rsc);
 
     /*!
      * \internal
      * \brief Add meta-attributes relevant to transition graph actions to XML
      *
      * If a given resource supports variant-specific meta-attributes that are
      * needed for transition graph actions, add them to a given XML element.
      *
      * \param[in]     rsc  Resource whose meta-attributes should be added
      * \param[in,out] xml  Transition graph action attributes XML to add to
      */
     void (*add_graph_meta)(const pcmk_resource_t *rsc, xmlNode *xml);
 
     /*!
      * \internal
      * \brief Add a resource's utilization to a table of utilization values
      *
      * This function is used when summing the utilization of a resource and all
      * resources colocated with it, to determine whether a node has sufficient
      * capacity. Given a resource and a table of utilization values, it will add
      * the resource's utilization to the existing values, if the resource has
      * not yet been assigned to a node.
      *
      * \param[in]     rsc          Resource with utilization to add
      * \param[in]     orig_rsc     Resource being assigned (for logging only)
      * \param[in]     all_rscs     List of all resources that will be summed
      * \param[in,out] utilization  Table of utilization values to add to
      */
     void (*add_utilization)(const pcmk_resource_t *rsc,
                             const pcmk_resource_t *orig_rsc, GList *all_rscs,
                             GHashTable *utilization);
 
     /*!
      * \internal
      * \brief Apply a shutdown lock for a resource, if appropriate
      *
      * \param[in,out] rsc       Resource to check for shutdown lock
      */
     void (*shutdown_lock)(pcmk_resource_t *rsc);
 };
 
 // Actions (pcmk_sched_actions.c)
 
 G_GNUC_INTERNAL
 void pcmk__update_action_for_orderings(pcmk_action_t *action,
                                        pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 uint32_t pcmk__update_ordered_actions(pcmk_action_t *first, pcmk_action_t *then,
                                       const pcmk_node_t *node, uint32_t flags,
                                       uint32_t filter, uint32_t type,
                                       pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 void pcmk__log_action(const char *pre_text, const pcmk_action_t *action,
                       bool details);
 
 G_GNUC_INTERNAL
 pcmk_action_t *pcmk__new_cancel_action(pcmk_resource_t *rsc, const char *name,
                                        guint interval_ms,
                                        const pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 pcmk_action_t *pcmk__new_shutdown_action(pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 bool pcmk__action_locks_rsc_to_node(const pcmk_action_t *action);
 
 G_GNUC_INTERNAL
 void pcmk__deduplicate_action_inputs(pcmk_action_t *action);
 
 G_GNUC_INTERNAL
 void pcmk__output_actions(pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 bool pcmk__check_action_config(pcmk_resource_t *rsc, pcmk_node_t *node,
                                const xmlNode *xml_op);
 
 G_GNUC_INTERNAL
 void pcmk__handle_rsc_config_changes(pcmk_scheduler_t *scheduler);
 
 
 // Recurring actions (pcmk_sched_recurring.c)
 
 G_GNUC_INTERNAL
 void pcmk__create_recurring_actions(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__schedule_cancel(pcmk_resource_t *rsc, const char *call_id,
                            const char *task, guint interval_ms,
                            const pcmk_node_t *node, const char *reason);
 
 G_GNUC_INTERNAL
 void pcmk__reschedule_recurring(pcmk_resource_t *rsc, const char *task,
                                 guint interval_ms, pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 bool pcmk__action_is_recurring(const pcmk_action_t *action);
 
 
 // Producing transition graphs (pcmk_graph_producer.c)
 
 G_GNUC_INTERNAL
 bool pcmk__graph_has_loop(const pcmk_action_t *init_action,
                           const pcmk_action_t *action,
                           pcmk__related_action_t *input);
 
 G_GNUC_INTERNAL
 void pcmk__add_rsc_actions_to_graph(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__create_graph(pcmk_scheduler_t *scheduler);
 
 
 // Fencing (pcmk_sched_fencing.c)
 
 G_GNUC_INTERNAL
 void pcmk__order_vs_fence(pcmk_action_t *stonith_op,
                           pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 void pcmk__order_vs_unfence(const pcmk_resource_t *rsc, pcmk_node_t *node,
                             pcmk_action_t *action,
                             enum pcmk__action_relation_flags order);
 
 G_GNUC_INTERNAL
 void pcmk__fence_guest(pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 bool pcmk__node_unfenced(const pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 void pcmk__order_restart_vs_unfence(gpointer data, gpointer user_data);
 
 
 // Injected scheduler inputs (pcmk_sched_injections.c)
 
 G_GNUC_INTERNAL
 void pcmk__inject_scheduler_input(pcmk_scheduler_t *scheduler, cib_t *cib,
                                   const pcmk_injections_t *injections);
 
 
 // Constraints of any type (pcmk_sched_constraints.c)
 
 G_GNUC_INTERNAL
 pcmk_resource_t *pcmk__find_constraint_resource(GList *rsc_list,
                                                 const char *id);
 
 G_GNUC_INTERNAL
 int pcmk__parse_constraint_role(const char *id, const char *role_spec,
                                 enum rsc_role_e *role);
 
 G_GNUC_INTERNAL
 xmlNode *pcmk__expand_tags_in_sets(xmlNode *xml_obj,
                                    const pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 bool pcmk__valid_resource_or_tag(const pcmk_scheduler_t *scheduler,
                                  const char *id, pcmk_resource_t **rsc,
                                  pcmk__idref_t **tag);
 
 G_GNUC_INTERNAL
 bool pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr,
                       bool convert_rsc, const pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 void pcmk__create_internal_constraints(pcmk_scheduler_t *scheduler);
 
 
 // Location constraints
 
 G_GNUC_INTERNAL
 void pcmk__unpack_location(xmlNode *xml_obj, pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 pcmk__location_t *pcmk__new_location(const char *id, pcmk_resource_t *rsc,
                                      int node_score, const char *discover_mode,
                                      pcmk_node_t *foo_node);
 
 G_GNUC_INTERNAL
 void pcmk__apply_locations(pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 void pcmk__apply_location(pcmk_resource_t *rsc, pcmk__location_t *constraint);
 
 
 // Colocation constraints (pcmk_sched_colocation.c)
 
 enum pcmk__coloc_affects {
     pcmk__coloc_affects_nothing = 0,
     pcmk__coloc_affects_location,
     pcmk__coloc_affects_role,
 };
 
 G_GNUC_INTERNAL
 const char *pcmk__colocation_node_attr(const pcmk_node_t *node,
                                        const char *attr,
                                        const pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 enum pcmk__coloc_affects pcmk__colocation_affects(const pcmk_resource_t
                                                     *dependent,
                                                   const pcmk_resource_t
                                                     *primary,
                                                   const pcmk__colocation_t
                                                     *colocation,
                                                   bool preview);
 
 G_GNUC_INTERNAL
 void pcmk__apply_coloc_to_scores(pcmk_resource_t *dependent,
                                  const pcmk_resource_t *primary,
                                  const pcmk__colocation_t *colocation);
 
 G_GNUC_INTERNAL
 int pcmk__apply_coloc_to_priority(pcmk_resource_t *dependent,
                                   const pcmk_resource_t *primary,
                                   const pcmk__colocation_t *colocation);
 
 G_GNUC_INTERNAL
 void pcmk__add_colocated_node_scores(pcmk_resource_t *source_rsc,
                                      const pcmk_resource_t *target_rsc,
                                      const char *log_id, GHashTable **nodes,
                                      const pcmk__colocation_t *colocation,
                                      float factor, uint32_t flags);
 
 G_GNUC_INTERNAL
 void pcmk__add_dependent_scores(gpointer data, gpointer user_data);
 
 G_GNUC_INTERNAL
 void pcmk__colocation_intersect_nodes(pcmk_resource_t *dependent,
                                       const pcmk_resource_t *primary,
                                       const pcmk__colocation_t *colocation,
                                       const GList *primary_nodes,
                                       bool merge_scores);
 
 G_GNUC_INTERNAL
 void pcmk__unpack_colocation(xmlNode *xml_obj, pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 void pcmk__add_this_with(GList **list, const pcmk__colocation_t *colocation,
                          const pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__add_this_with_list(GList **list, GList *addition,
                               const pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__add_with_this(GList **list, const pcmk__colocation_t *colocation,
                          const pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__add_with_this_list(GList **list, GList *addition,
                               const pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 GList *pcmk__with_this_colocations(const pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 GList *pcmk__this_with_colocations(const pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__new_colocation(const char *id, const char *node_attr, int score,
                           pcmk_resource_t *dependent, pcmk_resource_t *primary,
                           const char *dependent_role_spec,
                           const char *primary_role_spec, uint32_t flags);
 
 G_GNUC_INTERNAL
 void pcmk__block_colocation_dependents(pcmk_action_t *action);
 
 G_GNUC_INTERNAL
 bool pcmk__colocation_has_influence(const pcmk__colocation_t *colocation,
                                     const pcmk_resource_t *rsc);
 
 
 // Ordering constraints (pcmk_sched_ordering.c)
 
 G_GNUC_INTERNAL
 void pcmk__new_ordering(pcmk_resource_t *first_rsc, char *first_task,
                         pcmk_action_t *first_action, pcmk_resource_t *then_rsc,
                         char *then_task, pcmk_action_t *then_action,
                         uint32_t flags, pcmk_scheduler_t *sched);
 
 G_GNUC_INTERNAL
 void pcmk__unpack_ordering(xmlNode *xml_obj, pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 void pcmk__disable_invalid_orderings(pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 void pcmk__order_stops_before_shutdown(pcmk_node_t *node,
                                        pcmk_action_t *shutdown_op);
 
 G_GNUC_INTERNAL
 void pcmk__apply_orderings(pcmk_scheduler_t *sched);
 
 G_GNUC_INTERNAL
 void pcmk__order_after_each(pcmk_action_t *after, GList *list);
 
 
 /*!
  * \internal
  * \brief Create a new ordering between two resource actions
  *
  * \param[in,out] first_rsc   Resource for 'first' action
  * \param[in,out] first_task  Action key for 'first' action
  * \param[in]     then_rsc    Resource for 'then' action
  * \param[in,out] then_task   Action key for 'then' action
  * \param[in]     flags       Group of enum pcmk__action_relation_flags
  */
 #define pcmk__order_resource_actions(first_rsc, first_task,                 \
                                      then_rsc, then_task, flags)            \
     pcmk__new_ordering((first_rsc),                                         \
                        pcmk__op_key((first_rsc)->id, (first_task), 0),      \
                        NULL,                                                \
                        (then_rsc),                                          \
                        pcmk__op_key((then_rsc)->id, (then_task), 0),        \
                        NULL, (flags), (first_rsc)->priv->scheduler)
 
 #define pcmk__order_starts(rsc1, rsc2, flags)                \
     pcmk__order_resource_actions((rsc1), PCMK_ACTION_START,  \
                                  (rsc2), PCMK_ACTION_START, (flags))
 
 #define pcmk__order_stops(rsc1, rsc2, flags)                 \
     pcmk__order_resource_actions((rsc1), PCMK_ACTION_STOP,   \
                                  (rsc2), PCMK_ACTION_STOP, (flags))
 
 
 // Ticket constraints (pcmk_sched_tickets.c)
 
 G_GNUC_INTERNAL
 void pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler);
 
 
 // Promotable clone resources (pcmk_sched_promotable.c)
 
 G_GNUC_INTERNAL
 void pcmk__add_promotion_scores(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__require_promotion_tickets(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__set_instance_roles(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__create_promotable_actions(pcmk_resource_t *clone);
 
 G_GNUC_INTERNAL
 void pcmk__promotable_restart_ordering(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__order_promotable_instances(pcmk_resource_t *clone);
 
 G_GNUC_INTERNAL
 void pcmk__update_dependent_with_promotable(const pcmk_resource_t *primary,
                                             pcmk_resource_t *dependent,
                                             const pcmk__colocation_t
                                                 *colocation);
 
 G_GNUC_INTERNAL
 int pcmk__update_promotable_dependent_priority(const pcmk_resource_t *primary,
                                                pcmk_resource_t *dependent,
                                                const pcmk__colocation_t
                                                    *colocation);
 
 
 // Pacemaker Remote nodes (pcmk_sched_remote.c)
 
 G_GNUC_INTERNAL
 bool pcmk__is_failed_remote_node(const pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 void pcmk__order_remote_connection_actions(pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 bool pcmk__rsc_corresponds_to_guest(const pcmk_resource_t *rsc,
                                     const pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 pcmk_node_t *pcmk__connection_host_for_action(const pcmk_action_t *action);
 
 G_GNUC_INTERNAL
 void pcmk__substitute_remote_addr(pcmk_resource_t *rsc, GHashTable *params);
 
 G_GNUC_INTERNAL
 void pcmk__add_guest_meta_to_xml(xmlNode *args_xml,
                                  const pcmk_action_t *action);
 
 
 // Primitives (pcmk_sched_primitive.c)
 
 G_GNUC_INTERNAL
 pcmk_node_t *pcmk__primitive_assign(pcmk_resource_t *rsc,
                                     const pcmk_node_t *prefer,
                                     bool stop_if_fail);
 
 G_GNUC_INTERNAL
 void pcmk__primitive_create_actions(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__primitive_internal_constraints(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 uint32_t pcmk__primitive_action_flags(pcmk_action_t *action,
                                       const pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 int pcmk__primitive_apply_coloc_score(pcmk_resource_t *dependent,
                                       const pcmk_resource_t *primary,
                                       const pcmk__colocation_t *colocation,
                                       bool for_dependent);
 
 G_GNUC_INTERNAL
 void pcmk__with_primitive_colocations(const pcmk_resource_t *rsc,
                                       const pcmk_resource_t *orig_rsc,
                                       GList **list);
 
 G_GNUC_INTERNAL
 void pcmk__primitive_with_colocations(const pcmk_resource_t *rsc,
                                       const pcmk_resource_t *orig_rsc,
                                       GList **list);
 
 G_GNUC_INTERNAL
 void pcmk__schedule_cleanup(pcmk_resource_t *rsc, const pcmk_node_t *node,
                             bool optional);
 
 G_GNUC_INTERNAL
 void pcmk__primitive_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml);
 
 G_GNUC_INTERNAL
 void pcmk__primitive_add_utilization(const pcmk_resource_t *rsc,
                                      const pcmk_resource_t *orig_rsc,
                                      GList *all_rscs, GHashTable *utilization);
 
 G_GNUC_INTERNAL
 void pcmk__primitive_shutdown_lock(pcmk_resource_t *rsc);
 
 
 // Groups (pcmk_sched_group.c)
 
 G_GNUC_INTERNAL
 pcmk_node_t *pcmk__group_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
                                 bool stop_if_fail);
 
 G_GNUC_INTERNAL
 void pcmk__group_create_actions(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__group_internal_constraints(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 int pcmk__group_apply_coloc_score(pcmk_resource_t *dependent,
                                   const pcmk_resource_t *primary,
                                   const pcmk__colocation_t *colocation,
                                   bool for_dependent);
 
 G_GNUC_INTERNAL
 void pcmk__with_group_colocations(const pcmk_resource_t *rsc,
                                   const pcmk_resource_t *orig_rsc,
                                   GList **list);
 
 G_GNUC_INTERNAL
 void pcmk__group_with_colocations(const pcmk_resource_t *rsc,
                                   const pcmk_resource_t *orig_rsc,
                                   GList **list);
 
 G_GNUC_INTERNAL
 void pcmk__group_add_colocated_node_scores(pcmk_resource_t *source_rsc,
                                            const pcmk_resource_t *target_rsc,
                                            const char *log_id,
                                            GHashTable **nodes,
                                            const pcmk__colocation_t *colocation,
                                            float factor, uint32_t flags);
 
 G_GNUC_INTERNAL
 void pcmk__group_apply_location(pcmk_resource_t *rsc,
                                 pcmk__location_t *location);
 
 G_GNUC_INTERNAL
 uint32_t pcmk__group_action_flags(pcmk_action_t *action,
                                   const pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 uint32_t pcmk__group_update_ordered_actions(pcmk_action_t *first,
                                             pcmk_action_t *then,
                                             const pcmk_node_t *node,
                                             uint32_t flags, uint32_t filter,
                                             uint32_t type,
                                             pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 GList *pcmk__group_colocated_resources(const pcmk_resource_t *rsc,
                                        const pcmk_resource_t *orig_rsc,
                                        GList *colocated_rscs);
 
 G_GNUC_INTERNAL
 void pcmk__group_add_utilization(const pcmk_resource_t *rsc,
                                  const pcmk_resource_t *orig_rsc,
                                  GList *all_rscs, GHashTable *utilization);
 
 G_GNUC_INTERNAL
 void pcmk__group_shutdown_lock(pcmk_resource_t *rsc);
 
 
 // Clones (pcmk_sched_clone.c)
 
 G_GNUC_INTERNAL
 pcmk_node_t *pcmk__clone_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
                                 bool stop_if_fail);
 
 G_GNUC_INTERNAL
 void pcmk__clone_create_actions(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 bool pcmk__clone_create_probe(pcmk_resource_t *rsc, pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 void pcmk__clone_internal_constraints(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 int pcmk__clone_apply_coloc_score(pcmk_resource_t *dependent,
                                   const pcmk_resource_t *primary,
                                   const pcmk__colocation_t *colocation,
                                   bool for_dependent);
 
 G_GNUC_INTERNAL
 void pcmk__with_clone_colocations(const pcmk_resource_t *rsc,
                                   const pcmk_resource_t *orig_rsc,
                                   GList **list);
 
 G_GNUC_INTERNAL
 void pcmk__clone_with_colocations(const pcmk_resource_t *rsc,
                                   const pcmk_resource_t *orig_rsc,
                                   GList **list);
 
 G_GNUC_INTERNAL
 void pcmk__clone_apply_location(pcmk_resource_t *rsc,
                                 pcmk__location_t *constraint);
 
 G_GNUC_INTERNAL
 uint32_t pcmk__clone_action_flags(pcmk_action_t *action,
                                   const pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 void pcmk__clone_add_actions_to_graph(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__clone_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml);
 
 G_GNUC_INTERNAL
 void pcmk__clone_add_utilization(const pcmk_resource_t *rsc,
                                  const pcmk_resource_t *orig_rsc,
                                  GList *all_rscs, GHashTable *utilization);
 
 G_GNUC_INTERNAL
 void pcmk__clone_shutdown_lock(pcmk_resource_t *rsc);
 
 // Bundles (pcmk_sched_bundle.c)
 
 G_GNUC_INTERNAL
 pcmk_node_t *pcmk__bundle_assign(pcmk_resource_t *rsc,
                                  const pcmk_node_t *prefer, bool stop_if_fail);
 
 G_GNUC_INTERNAL
 void pcmk__bundle_create_actions(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 bool pcmk__bundle_create_probe(pcmk_resource_t *rsc, pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 void pcmk__bundle_internal_constraints(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 int pcmk__bundle_apply_coloc_score(pcmk_resource_t *dependent,
                                    const pcmk_resource_t *primary,
                                    const pcmk__colocation_t *colocation,
                                    bool for_dependent);
 
 G_GNUC_INTERNAL
 void pcmk__with_bundle_colocations(const pcmk_resource_t *rsc,
                                    const pcmk_resource_t *orig_rsc,
                                    GList **list);
 
 G_GNUC_INTERNAL
 void pcmk__bundle_with_colocations(const pcmk_resource_t *rsc,
                                    const pcmk_resource_t *orig_rsc,
                                    GList **list);
 
 G_GNUC_INTERNAL
 void pcmk__bundle_apply_location(pcmk_resource_t *rsc,
                                  pcmk__location_t *constraint);
 
 G_GNUC_INTERNAL
 uint32_t pcmk__bundle_action_flags(pcmk_action_t *action,
                                    const pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 void pcmk__output_bundle_actions(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__bundle_add_actions_to_graph(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__bundle_add_utilization(const pcmk_resource_t *rsc,
                                   const pcmk_resource_t *orig_rsc,
                                   GList *all_rscs, GHashTable *utilization);
 
 G_GNUC_INTERNAL
 void pcmk__bundle_shutdown_lock(pcmk_resource_t *rsc);
 
 
 // Clone instances or bundle replica containers (pcmk_sched_instances.c)
 
 G_GNUC_INTERNAL
 void pcmk__assign_instances(pcmk_resource_t *collective, GList *instances,
                             int max_total, int max_per_node);
 
 G_GNUC_INTERNAL
 void pcmk__create_instance_actions(pcmk_resource_t *rsc, GList *instances);
 
 G_GNUC_INTERNAL
 bool pcmk__instance_matches(const pcmk_resource_t *instance,
                             const pcmk_node_t *node, enum rsc_role_e role,
                             bool current);
 
 G_GNUC_INTERNAL
 pcmk_resource_t *pcmk__find_compatible_instance(const pcmk_resource_t *match_rsc,
                                                 const pcmk_resource_t *rsc,
                                                 enum rsc_role_e role,
                                                 bool current);
 
 G_GNUC_INTERNAL
 uint32_t pcmk__instance_update_ordered_actions(pcmk_action_t *first,
                                                pcmk_action_t *then,
                                                const pcmk_node_t *node,
                                                uint32_t flags, uint32_t filter,
                                                uint32_t type,
                                                pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 uint32_t pcmk__collective_action_flags(pcmk_action_t *action,
                                        const GList *instances,
                                        const pcmk_node_t *node);
 
 
 // Injections (pcmk_injections.c)
 
 G_GNUC_INTERNAL
 xmlNode *pcmk__inject_node(cib_t *cib_conn, const char *node, const char *uuid);
 
 G_GNUC_INTERNAL
 xmlNode *pcmk__inject_node_state_change(cib_t *cib_conn, const char *node,
                                         bool up);
 
 G_GNUC_INTERNAL
 xmlNode *pcmk__inject_resource_history(pcmk__output_t *out, xmlNode *cib_node,
                                        const char *resource,
                                        const char *lrm_name,
                                        const char *rclass,
                                        const char *rtype,
                                        const char *rprovider);
 
 G_GNUC_INTERNAL
 void pcmk__inject_failcount(pcmk__output_t *out, cib_t *cib_conn,
                             xmlNode *cib_node, const char *resource,
-                            const char *task, guint interval_ms, int rc);
+                            const char *task, guint interval_ms, int rc,
+                            bool infinity);
 
 G_GNUC_INTERNAL
 xmlNode *pcmk__inject_action_result(xmlNode *cib_resource,
                                     lrmd_event_data_t *op, const char *node,
                                     int target_rc);
 
 
 // Nodes (pcmk_sched_nodes.c)
 
 G_GNUC_INTERNAL
 bool pcmk__node_available(const pcmk_node_t *node, bool consider_score,
                           bool consider_guest);
 
 G_GNUC_INTERNAL
 bool pcmk__any_node_available(GHashTable *nodes);
 
 G_GNUC_INTERNAL
 GHashTable *pcmk__copy_node_table(GHashTable *nodes);
 
 G_GNUC_INTERNAL
 void pcmk__copy_node_tables(const pcmk_resource_t *rsc, GHashTable **copy);
 
 G_GNUC_INTERNAL
 void pcmk__restore_node_tables(pcmk_resource_t *rsc, GHashTable *backup);
 
 G_GNUC_INTERNAL
 GList *pcmk__sort_nodes(GList *nodes, pcmk_node_t *active_node);
 
 G_GNUC_INTERNAL
 void pcmk__apply_node_health(pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 pcmk_node_t *pcmk__top_allowed_node(const pcmk_resource_t *rsc,
                                     const pcmk_node_t *node);
 
 
 // Functions applying to more than one variant (pcmk_sched_resource.c)
 
 G_GNUC_INTERNAL
 void pcmk__set_assignment_methods(pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 bool pcmk__rsc_agent_changed(pcmk_resource_t *rsc, pcmk_node_t *node,
                              const xmlNode *rsc_entry, bool active_on_node);
 
 G_GNUC_INTERNAL
 GList *pcmk__rscs_matching_id(const char *id,
                               const pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 GList *pcmk__colocated_resources(const pcmk_resource_t *rsc,
                                  const pcmk_resource_t *orig_rsc,
                                  GList *colocated_rscs);
 
 G_GNUC_INTERNAL
 void pcmk__noop_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml);
 
 G_GNUC_INTERNAL
 void pcmk__output_resource_actions(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 bool pcmk__assign_resource(pcmk_resource_t *rsc, pcmk_node_t *node, bool force,
                            bool stop_if_fail);
 
 G_GNUC_INTERNAL
 void pcmk__unassign_resource(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 bool pcmk__threshold_reached(pcmk_resource_t *rsc, const pcmk_node_t *node,
                              pcmk_resource_t **failed);
 
 G_GNUC_INTERNAL
 void pcmk__sort_resources(pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 gint pcmk__cmp_instance(gconstpointer a, gconstpointer b);
 
 G_GNUC_INTERNAL
 gint pcmk__cmp_instance_number(gconstpointer a, gconstpointer b);
 
 
 // Functions related to probes (pcmk_sched_probes.c)
 
 G_GNUC_INTERNAL
 bool pcmk__probe_rsc_on_node(pcmk_resource_t *rsc, pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 void pcmk__order_probes(pcmk_scheduler_t *scheduler);
 
 G_GNUC_INTERNAL
 bool pcmk__probe_resource_list(GList *rscs, pcmk_node_t *node);
 
 G_GNUC_INTERNAL
 void pcmk__schedule_probes(pcmk_scheduler_t *scheduler);
 
 
 // Functions related to live migration (pcmk_sched_migration.c)
 
 void pcmk__create_migration_actions(pcmk_resource_t *rsc,
                                     const pcmk_node_t *current);
 
 void pcmk__abort_dangling_migration(void *data, void *user_data);
 
 bool pcmk__rsc_can_migrate(const pcmk_resource_t *rsc,
                            const pcmk_node_t *current);
 
 void pcmk__order_migration_equivalents(pcmk__action_relation_t *order);
 
 
 // Functions related to node utilization (pcmk_sched_utilization.c)
 
 G_GNUC_INTERNAL
 int pcmk__compare_node_capacities(const pcmk_node_t *node1,
                                   const pcmk_node_t *node2);
 
 G_GNUC_INTERNAL
 void pcmk__consume_node_capacity(GHashTable *current_utilization,
                                  const pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__release_node_capacity(GHashTable *current_utilization,
                                  const pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 const pcmk_node_t *pcmk__ban_insufficient_capacity(pcmk_resource_t *rsc);
 
 G_GNUC_INTERNAL
 void pcmk__create_utilization_constraints(pcmk_resource_t *rsc,
                                           const GList *allowed_nodes);
 
 G_GNUC_INTERNAL
 void pcmk__show_node_capacities(const char *desc, pcmk_scheduler_t *scheduler);
 
 
 // Functions related to the scheduler (pcmk_scheduler.c)
 
 G_GNUC_INTERNAL
 int pcmk__init_scheduler(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date,
                          pcmk_scheduler_t **scheduler);
 
 
 // General setup functions (pcmk_setup.c)
 
 G_GNUC_INTERNAL
 int pcmk__setup_output_cib_sched(pcmk__output_t **out, cib_t **cib,
                                  pcmk_scheduler_t **scheduler, xmlNode **xml);
 
 G_GNUC_INTERNAL
 int pcmk__setup_output_fencing(pcmk__output_t **out, stonith_t **st, xmlNode **xml);
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif // PCMK__PACEMAKER_LIBPACEMAKER_PRIVATE__H
diff --git a/lib/pacemaker/pcmk_injections.c b/lib/pacemaker/pcmk_injections.c
index d249ae7bb0..d728cfea1e 100644
--- a/lib/pacemaker/pcmk_injections.c
+++ b/lib/pacemaker/pcmk_injections.c
@@ -1,777 +1,796 @@
 /*
  * Copyright 2009-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 <stdio.h>
 #include <unistd.h>
 #include <stdlib.h>
 
 #include <sys/stat.h>
 #include <sys/param.h>
 #include <sys/types.h>
 #include <dirent.h>
 
 #include <crm/crm.h>
 #include <crm/cib.h>
 #include <crm/cib/internal.h>
 #include <crm/common/util.h>
 #include <crm/common/iso8601.h>
 #include <crm/common/xml_internal.h>
 #include <crm/lrmd_events.h>            // lrmd_event_data_t, etc.
 #include <crm/lrmd_internal.h>
 #include <crm/pengine/status.h>
 #include <pacemaker-internal.h>
 
 #include "libpacemaker_private.h"
 
 bool pcmk__simulate_node_config = false;
 
 #define XPATH_NODE_CONFIG   "//" PCMK_XE_NODE "[@" PCMK_XA_UNAME "='%s']"
 #define XPATH_NODE_STATE    "//" PCMK__XE_NODE_STATE "[@" PCMK_XA_UNAME "='%s']"
 #define XPATH_NODE_STATE_BY_ID "//" PCMK__XE_NODE_STATE "[@" PCMK_XA_ID "='%s']"
 #define XPATH_RSC_HISTORY   XPATH_NODE_STATE \
                             "//" PCMK__XE_LRM_RESOURCE "[@" PCMK_XA_ID "='%s']"
 
 
 /*!
  * \internal
  * \brief Inject a fictitious transient node attribute into scheduler input
  *
  * \param[in,out] out       Output object for displaying error messages
  * \param[in,out] cib_node  \c PCMK__XE_NODE_STATE XML to inject attribute into
  * \param[in]     name      Transient node attribute name to inject
  * \param[in]     value     Transient node attribute value to inject
  */
 static void
 inject_transient_attr(pcmk__output_t *out, xmlNode *cib_node,
                       const char *name, const char *value)
 {
     xmlNode *attrs = NULL;
     xmlNode *instance_attrs = NULL;
     const char *node_uuid = pcmk__xe_id(cib_node);
 
     out->message(out, "inject-attr", name, value, cib_node);
 
     attrs = pcmk__xe_first_child(cib_node, PCMK__XE_TRANSIENT_ATTRIBUTES, NULL,
                                  NULL);
     if (attrs == NULL) {
         attrs = pcmk__xe_create(cib_node, PCMK__XE_TRANSIENT_ATTRIBUTES);
         crm_xml_add(attrs, PCMK_XA_ID, node_uuid);
     }
 
     instance_attrs = pcmk__xe_first_child(attrs, PCMK_XE_INSTANCE_ATTRIBUTES,
                                           NULL, NULL);
     if (instance_attrs == NULL) {
         instance_attrs = pcmk__xe_create(attrs, PCMK_XE_INSTANCE_ATTRIBUTES);
         crm_xml_add(instance_attrs, PCMK_XA_ID, node_uuid);
     }
 
     crm_create_nvpair_xml(instance_attrs, NULL, name, value);
 }
 
 /*!
  * \internal
  * \brief Inject a fictitious fail count into a scheduler input
  *
  * \param[in,out] out          Output object for displaying error messages
  * \param[in,out] cib_conn     CIB connection
  * \param[in,out] cib_node     Node state XML to inject into
  * \param[in]     resource     ID of resource for fail count to inject
  * \param[in]     task         Action name for fail count to inject
  * \param[in]     interval_ms  Action interval (in milliseconds) for fail count
  * \param[in]     exit_status  Action result for fail count to inject (if
  *                             \c PCMK_OCF_OK, or \c PCMK_OCF_NOT_RUNNING when
  *                             \p interval_ms is 0, inject nothing)
+ * \param[in]     infinity     If true, set fail count to "INFINITY", otherwise
+ *                             increase it by 1
  */
 void
 pcmk__inject_failcount(pcmk__output_t *out, cib_t *cib_conn, xmlNode *cib_node,
                        const char *resource, const char *task,
-                       guint interval_ms, int exit_status)
+                       guint interval_ms, int exit_status, bool infinity)
 {
     char *name = NULL;
     char *value = NULL;
 
     int failcount = 0;
     xmlNode *output = NULL;
 
     CRM_CHECK((out != NULL) && (cib_conn != NULL) && (cib_node != NULL)
               && (resource != NULL) && (task != NULL), return);
 
     if ((exit_status == PCMK_OCF_OK)
         || ((exit_status == PCMK_OCF_NOT_RUNNING) && (interval_ms == 0))) {
         return;
     }
 
     // Get current failcount and increment it
     name = pcmk__failcount_name(resource, task, interval_ms);
 
     if (cib__get_node_attrs(out, cib_conn, PCMK_XE_STATUS,
                             pcmk__xe_id(cib_node), NULL, NULL, NULL, name,
                             NULL, &output) == pcmk_rc_ok) {
 
         if (crm_element_value_int(output, PCMK_XA_VALUE, &failcount) != 0) {
             failcount = 0;
         }
     }
-    value = pcmk__itoa(failcount + 1);
+
+    if (infinity) {
+        value = pcmk__str_copy(PCMK_VALUE_INFINITY);
+
+    } else {
+        value = pcmk__itoa(failcount + 1);
+    }
+
     inject_transient_attr(out, cib_node, name, value);
 
     free(name);
     free(value);
     pcmk__xml_free(output);
 
     name = pcmk__lastfailure_name(resource, task, interval_ms);
     value = pcmk__ttoa(time(NULL));
     inject_transient_attr(out, cib_node, name, value);
 
     free(name);
     free(value);
 }
 
 /*!
  * \internal
  * \brief Create a CIB configuration entry for a fictitious node
  *
  * \param[in,out] cib_conn  CIB object to use
  * \param[in]     node      Node name to use
  */
 static void
 create_node_entry(cib_t *cib_conn, const char *node)
 {
     int rc = pcmk_ok;
     char *xpath = crm_strdup_printf(XPATH_NODE_CONFIG, node);
 
     rc = cib_conn->cmds->query(cib_conn, xpath, NULL, cib_xpath|cib_sync_call);
 
     if (rc == -ENXIO) { // Only add if not already existing
         xmlNode *cib_object = pcmk__xe_create(NULL, PCMK_XE_NODE);
 
         crm_xml_add(cib_object, PCMK_XA_ID, node); // Use node name as ID
         crm_xml_add(cib_object, PCMK_XA_UNAME, node);
         cib_conn->cmds->create(cib_conn, PCMK_XE_NODES, cib_object,
                                cib_sync_call);
         /* Not bothering with subsequent query to see if it exists,
            we'll bomb out later in the call to query_node_uuid()... */
 
         pcmk__xml_free(cib_object);
     }
 
     free(xpath);
 }
 
 /*!
  * \internal
  * \brief Synthesize a fake executor event for an action
  *
  * \param[in] cib_resource  XML for any existing resource action history
  * \param[in] task          Name of action to synthesize
  * \param[in] interval_ms   Interval of action to synthesize
  * \param[in] outcome       Result of action to synthesize
  *
  * \return Newly allocated executor event
  * \note It is the caller's responsibility to free the result with
  *       lrmd_free_event().
  */
 static lrmd_event_data_t *
 create_op(const xmlNode *cib_resource, const char *task, guint interval_ms,
           int outcome)
 {
     lrmd_event_data_t *op = NULL;
     xmlNode *xop = NULL;
 
     op = lrmd_new_event(pcmk__xe_id(cib_resource), task, interval_ms);
     lrmd__set_result(op, outcome, PCMK_EXEC_DONE, "Simulated action result");
     op->params = NULL; // Not needed for simulation purposes
     op->t_run = time(NULL);
     op->t_rcchange = op->t_run;
 
     // Use a call ID higher than any existing history entries
     op->call_id = 0;
     for (xop = pcmk__xe_first_child(cib_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);
         if (tmp > op->call_id) {
             op->call_id = tmp;
         }
     }
     op->call_id++;
 
     return op;
 }
 
 /*!
  * \internal
  * \brief Inject a fictitious resource history entry into a scheduler input
  *
  * \param[in,out] cib_resource  Resource history XML to inject entry into
  * \param[in,out] op            Action result to inject
  * \param[in]     node          Name of node where the action occurred
  * \param[in]     target_rc     Expected result for action to inject
  *
  * \return XML of injected resource history entry
  */
 xmlNode *
 pcmk__inject_action_result(xmlNode *cib_resource, lrmd_event_data_t *op,
                            const char *node, int target_rc)
 {
     return pcmk__create_history_xml(cib_resource, op, CRM_FEATURE_SET,
                                     target_rc, node, crm_system_name);
 }
 
 /*!
  * \internal
  * \brief Inject a fictitious node into a scheduler input
  *
  * \param[in,out] cib_conn  Scheduler input CIB to inject node into
  * \param[in]     node      Name of node to inject
  * \param[in]     uuid      UUID of node to inject
  *
  * \return XML of \c PCMK__XE_NODE_STATE entry for new node
  * \note If the global pcmk__simulate_node_config has been set to true, a
  *       node entry in the configuration section will be added, as well as a
  *       node state entry in the status section.
  */
 xmlNode *
 pcmk__inject_node(cib_t *cib_conn, const char *node, const char *uuid)
 {
     int rc = pcmk_ok;
     xmlNode *cib_object = NULL;
     char *xpath = crm_strdup_printf(XPATH_NODE_STATE, node);
     bool duplicate = false;
     char *found_uuid = NULL;
 
     if (pcmk__simulate_node_config) {
         create_node_entry(cib_conn, node);
     }
 
     rc = cib_conn->cmds->query(cib_conn, xpath, &cib_object,
                                cib_xpath|cib_sync_call);
 
     if ((cib_object != NULL) && (pcmk__xe_id(cib_object) == NULL)) {
         crm_err("Detected multiple " PCMK__XE_NODE_STATE " entries for "
                 "xpath=%s, bailing",
                 xpath);
         duplicate = true;
         goto done;
     }
 
     if (rc == -ENXIO) {
         if (uuid == NULL) {
             query_node_uuid(cib_conn, node, &found_uuid, NULL);
         } else {
             found_uuid = strdup(uuid);
         }
 
         if (found_uuid) {
             char *xpath_by_uuid = crm_strdup_printf(XPATH_NODE_STATE_BY_ID,
                                                     found_uuid);
 
             /* It's possible that a PCMK__XE_NODE_STATE entry doesn't have a
              * PCMK_XA_UNAME yet
              */
             rc = cib_conn->cmds->query(cib_conn, xpath_by_uuid, &cib_object,
                                        cib_xpath|cib_sync_call);
 
             if ((cib_object != NULL) && (pcmk__xe_id(cib_object) == NULL)) {
                 crm_err("Can't inject node state for %s because multiple "
                         "state entries found for ID %s", node, found_uuid);
                 duplicate = true;
                 free(xpath_by_uuid);
                 goto done;
 
             } else if (cib_object != NULL) {
                 crm_xml_add(cib_object, PCMK_XA_UNAME, node);
 
                 rc = cib_conn->cmds->modify(cib_conn, PCMK_XE_STATUS,
                                             cib_object, cib_sync_call);
             }
 
             free(xpath_by_uuid);
         }
     }
 
     if (rc == -ENXIO) {
         cib_object = pcmk__xe_create(NULL, PCMK__XE_NODE_STATE);
         crm_xml_add(cib_object, PCMK_XA_ID, found_uuid);
         crm_xml_add(cib_object, PCMK_XA_UNAME, node);
         cib_conn->cmds->create(cib_conn, PCMK_XE_STATUS, cib_object,
                                cib_sync_call);
         pcmk__xml_free(cib_object);
 
         rc = cib_conn->cmds->query(cib_conn, xpath, &cib_object,
                                    cib_xpath|cib_sync_call);
         crm_trace("Injecting node state for %s (rc=%d)", node, rc);
     }
 
 done:
     free(found_uuid);
     free(xpath);
 
     if (duplicate) {
         crm_log_xml_warn(cib_object, "Duplicates");
         crm_exit(CRM_EX_SOFTWARE);
         return NULL; // not reached, but makes static analysis happy
     }
 
     pcmk__assert(rc == pcmk_ok);
     return cib_object;
 }
 
 /*!
  * \internal
  * \brief Inject a fictitious node state change into a scheduler input
  *
  * \param[in,out] cib_conn  Scheduler input CIB to inject into
  * \param[in]     node      Name of node to inject change for
  * \param[in]     up        If true, change state to online, otherwise offline
  *
  * \return XML of changed (or added) node state entry
  */
 xmlNode *
 pcmk__inject_node_state_change(cib_t *cib_conn, const char *node, bool up)
 {
     xmlNode *cib_node = pcmk__inject_node(cib_conn, node, NULL);
 
     if (up) {
         pcmk__xe_set_props(cib_node,
                            PCMK__XA_IN_CCM, PCMK_VALUE_TRUE,
                            PCMK_XA_CRMD, PCMK_VALUE_ONLINE,
                            PCMK__XA_JOIN, CRMD_JOINSTATE_MEMBER,
                            PCMK_XA_EXPECTED, CRMD_JOINSTATE_MEMBER,
                            NULL);
     } else {
         pcmk__xe_set_props(cib_node,
                            PCMK__XA_IN_CCM, PCMK_VALUE_FALSE,
                            PCMK_XA_CRMD, PCMK_VALUE_OFFLINE,
                            PCMK__XA_JOIN, CRMD_JOINSTATE_DOWN,
                            PCMK_XA_EXPECTED, CRMD_JOINSTATE_DOWN,
                            NULL);
     }
     crm_xml_add(cib_node, PCMK_XA_CRM_DEBUG_ORIGIN, crm_system_name);
     return cib_node;
 }
 
 /*!
  * \internal
  * \brief Check whether a node has history for a given resource
  *
  * \param[in,out] cib_node  Node state XML to check
  * \param[in]     resource  Resource name to check for
  *
  * \return Resource's \c PCMK__XE_LRM_RESOURCE XML entry beneath \p cib_node if
  *         found, otherwise \c NULL
  */
 static xmlNode *
 find_resource_xml(xmlNode *cib_node, const char *resource)
 {
     const char *node = crm_element_value(cib_node, PCMK_XA_UNAME);
     char *xpath = crm_strdup_printf(XPATH_RSC_HISTORY, node, resource);
     xmlNode *match = get_xpath_object(xpath, cib_node, LOG_TRACE);
 
     free(xpath);
     return match;
 }
 
 /*!
  * \internal
  * \brief Inject a resource history element into a scheduler input
  *
  * \param[in,out] out       Output object for displaying error messages
  * \param[in,out] cib_node  Node state XML to inject resource history entry into
  * \param[in]     resource  ID (in configuration) of resource to inject
  * \param[in]     lrm_name  ID as used in history (could be clone instance)
  * \param[in]     rclass    Resource agent class of resource to inject
  * \param[in]     rtype     Resource agent type of resource to inject
  * \param[in]     rprovider Resource agent provider of resource to inject
  *
  * \return XML of injected resource history element
  * \note If a history element already exists under either \p resource or
  *       \p lrm_name, this will return it rather than injecting a new one.
  */
 xmlNode *
 pcmk__inject_resource_history(pcmk__output_t *out, xmlNode *cib_node,
                               const char *resource, const char *lrm_name,
                               const char *rclass, const char *rtype,
                               const char *rprovider)
 {
     xmlNode *lrm = NULL;
     xmlNode *container = NULL;
     xmlNode *cib_resource = NULL;
 
     cib_resource = find_resource_xml(cib_node, resource);
     if (cib_resource != NULL) {
         /* If an existing LRM history entry uses the resource name,
          * continue using it, even if lrm_name is different.
          */
         return cib_resource;
     }
 
     // Check for history entry under preferred name
     if (strcmp(resource, lrm_name) != 0) {
         cib_resource = find_resource_xml(cib_node, lrm_name);
         if (cib_resource != NULL) {
             return cib_resource;
         }
     }
 
     if ((rclass == NULL) || (rtype == NULL)) {
         // @TODO query configuration for class, provider, type
         out->err(out,
                  "Resource %s not found in the status section of %s "
                  "(supply class and type to continue)",
                  resource, pcmk__xe_id(cib_node));
         return NULL;
 
     } else if (!pcmk__strcase_any_of(rclass,
                                      PCMK_RESOURCE_CLASS_OCF,
                                      PCMK_RESOURCE_CLASS_STONITH,
                                      PCMK_RESOURCE_CLASS_SERVICE,
                                      PCMK_RESOURCE_CLASS_SYSTEMD,
                                      PCMK_RESOURCE_CLASS_LSB, NULL)) {
         out->err(out, "Invalid class for %s: %s", resource, rclass);
         return NULL;
 
     } else if (pcmk_is_set(pcmk_get_ra_caps(rclass), pcmk_ra_cap_provider)
                && (rprovider == NULL)) {
         // @TODO query configuration for provider
         out->err(out, "Please specify the provider for resource %s", resource);
         return NULL;
     }
 
     crm_info("Injecting new resource %s into node state '%s'",
              lrm_name, pcmk__xe_id(cib_node));
 
     lrm = pcmk__xe_first_child(cib_node, PCMK__XE_LRM, NULL, NULL);
     if (lrm == NULL) {
         const char *node_uuid = pcmk__xe_id(cib_node);
 
         lrm = pcmk__xe_create(cib_node, PCMK__XE_LRM);
         crm_xml_add(lrm, PCMK_XA_ID, node_uuid);
     }
 
     container = pcmk__xe_first_child(lrm, PCMK__XE_LRM_RESOURCES, NULL, NULL);
     if (container == NULL) {
         container = pcmk__xe_create(lrm, PCMK__XE_LRM_RESOURCES);
     }
 
     cib_resource = pcmk__xe_create(container, PCMK__XE_LRM_RESOURCE);
 
     // If we're creating a new entry, use the preferred name
     crm_xml_add(cib_resource, PCMK_XA_ID, lrm_name);
 
     crm_xml_add(cib_resource, PCMK_XA_CLASS, rclass);
     crm_xml_add(cib_resource, PCMK_XA_PROVIDER, rprovider);
     crm_xml_add(cib_resource, PCMK_XA_TYPE, rtype);
 
     return cib_resource;
 }
 
 /*!
  * \internal
  * \brief Inject a ticket attribute into ticket state
  *
  * \param[in,out] out          Output object for displaying error messages
  * \param[in]     ticket_id    Ticket whose state should be changed
  * \param[in]     attr_name    Ticket attribute name to inject
  * \param[in]     attr_value   Boolean value of ticket attribute to inject
  * \param[in,out] cib          CIB object to use
  *
  * \return Standard Pacemaker return code
  */
 static int
 set_ticket_state_attr(pcmk__output_t *out, const char *ticket_id,
                       const char *attr_name, bool attr_value, cib_t *cib)
 {
     int rc = pcmk_rc_ok;
     xmlNode *xml_top = NULL;
     xmlNode *ticket_state_xml = NULL;
 
     // Check for an existing ticket state entry
     rc = pcmk__get_ticket_state(cib, ticket_id, &ticket_state_xml);
 
     if (rc == pcmk_rc_duplicate_id) {
         out->err(out, "Multiple " PCMK__XE_TICKET_STATE "s match ticket_id=%s",
                  ticket_id);
         rc = pcmk_rc_ok;
     }
 
     if (rc == pcmk_rc_ok) { // Ticket state found, use it
         crm_debug("Injecting attribute into existing ticket state %s",
                   ticket_id);
         xml_top = ticket_state_xml;
 
     } else if (rc == ENXIO) { // No ticket state, create it
         xmlNode *xml_obj = NULL;
 
         xml_top = pcmk__xe_create(NULL, PCMK_XE_STATUS);
         xml_obj = pcmk__xe_create(xml_top, PCMK_XE_TICKETS);
         ticket_state_xml = pcmk__xe_create(xml_obj, PCMK__XE_TICKET_STATE);
         crm_xml_add(ticket_state_xml, PCMK_XA_ID, ticket_id);
 
     } else { // Error
         return rc;
     }
 
     // Add the attribute to the ticket state
     pcmk__xe_set_bool_attr(ticket_state_xml, attr_name, attr_value);
     crm_log_xml_debug(xml_top, "Update");
 
     // Commit the change to the CIB
     rc = cib->cmds->modify(cib, PCMK_XE_STATUS, xml_top, cib_sync_call);
     rc = pcmk_legacy2rc(rc);
 
     pcmk__xml_free(xml_top);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Inject a fictitious action into the cluster
  *
  * \param[in,out] out       Output object for displaying error messages
  * \param[in]     spec      Action specification to inject
  * \param[in,out] cib       CIB object for scheduler input
  * \param[in]     scheduler  Scheduler data
  */
 static void
 inject_action(pcmk__output_t *out, const char *spec, cib_t *cib,
               const pcmk_scheduler_t *scheduler)
 {
     int rc;
     int outcome = PCMK_OCF_OK;
     guint interval_ms = 0;
 
     char *key = NULL;
     char *node = NULL;
     char *task = NULL;
     char *resource = NULL;
 
     const char *rtype = NULL;
     const char *rclass = NULL;
     const char *rprovider = NULL;
 
     xmlNode *cib_op = NULL;
     xmlNode *cib_node = NULL;
     xmlNode *cib_resource = NULL;
     const pcmk_resource_t *rsc = NULL;
     lrmd_event_data_t *op = NULL;
+    bool infinity = false;
 
     out->message(out, "inject-spec", spec);
 
     key = pcmk__assert_alloc(1, strlen(spec) + 1);
     node = pcmk__assert_alloc(1, strlen(spec) + 1);
     rc = sscanf(spec, "%[^@]@%[^=]=%d", key, node, &outcome);
     if (rc != 3) {
         out->err(out, "Invalid operation spec: %s.  Only found %d fields",
                  spec, rc);
         goto done;
     }
 
     parse_op_key(key, &resource, &task, &interval_ms);
 
     rsc = pe_find_resource(scheduler->priv->resources, resource);
     if (rsc == NULL) {
         out->err(out, "Invalid resource name: %s", resource);
         goto done;
     }
 
     rclass = crm_element_value(rsc->priv->xml, PCMK_XA_CLASS);
     rtype = crm_element_value(rsc->priv->xml, PCMK_XA_TYPE);
     rprovider = crm_element_value(rsc->priv->xml, PCMK_XA_PROVIDER);
 
     cib_node = pcmk__inject_node(cib, node, NULL);
     pcmk__assert(cib_node != NULL);
 
+    if (pcmk__str_eq(task, PCMK_ACTION_STOP, pcmk__str_none)) {
+        infinity = true;
+
+    } else if (pcmk__str_eq(task, PCMK_ACTION_START, pcmk__str_none)
+               && pcmk_is_set(scheduler->flags,
+                              pcmk__sched_start_failure_fatal)) {
+        infinity = true;
+    }
+
     pcmk__inject_failcount(out, cib, cib_node, resource, task, interval_ms,
-                           outcome);
+                           outcome, infinity);
 
     cib_resource = pcmk__inject_resource_history(out, cib_node,
                                                  resource, resource,
                                                  rclass, rtype, rprovider);
     pcmk__assert(cib_resource != NULL);
 
     op = create_op(cib_resource, task, interval_ms, outcome);
     pcmk__assert(op != NULL);
 
     cib_op = pcmk__inject_action_result(cib_resource, op, node, 0);
     pcmk__assert(cib_op != NULL);
     lrmd_free_event(op);
 
     rc = cib->cmds->modify(cib, PCMK_XE_STATUS, cib_node, cib_sync_call);
     pcmk__assert(rc == pcmk_ok);
 
 done:
     free(task);
     free(node);
     free(key);
 }
 
 /*!
  * \internal
  * \brief Inject fictitious scheduler inputs
  *
  * \param[in,out] scheduler   Scheduler data
  * \param[in,out] cib         CIB object for scheduler input to modify
  * \param[in]     injections  Injections to apply
  */
 void
 pcmk__inject_scheduler_input(pcmk_scheduler_t *scheduler, cib_t *cib,
                              const pcmk_injections_t *injections)
 {
     int rc = pcmk_ok;
     const GList *iter = NULL;
     xmlNode *cib_node = NULL;
     pcmk__output_t *out = scheduler->priv->out;
 
     out->message(out, "inject-modify-config", injections->quorum,
                  injections->watchdog);
     if (injections->quorum != NULL) {
         xmlNode *top = pcmk__xe_create(NULL, PCMK_XE_CIB);
 
         /* crm_xml_add(top, PCMK_XA_DC_UUID, dc_uuid);      */
         crm_xml_add(top, PCMK_XA_HAVE_QUORUM, injections->quorum);
 
         rc = cib->cmds->modify(cib, NULL, top, cib_sync_call);
         pcmk__assert(rc == pcmk_ok);
     }
 
     if (injections->watchdog != NULL) {
         rc = cib__update_node_attr(out, cib, cib_sync_call, PCMK_XE_CRM_CONFIG,
                                    NULL, NULL, NULL, NULL,
                                    PCMK_OPT_HAVE_WATCHDOG, injections->watchdog,
                                    NULL, NULL);
         pcmk__assert(rc == pcmk_rc_ok);
     }
 
     for (iter = injections->node_up; iter != NULL; iter = iter->next) {
         const char *node = (const char *) iter->data;
 
         out->message(out, "inject-modify-node", "Online", node);
 
         cib_node = pcmk__inject_node_state_change(cib, node, true);
         pcmk__assert(cib_node != NULL);
 
         rc = cib->cmds->modify(cib, PCMK_XE_STATUS, cib_node, cib_sync_call);
         pcmk__assert(rc == pcmk_ok);
         pcmk__xml_free(cib_node);
     }
 
     for (iter = injections->node_down; iter != NULL; iter = iter->next) {
         const char *node = (const char *) iter->data;
         char *xpath = NULL;
 
         out->message(out, "inject-modify-node", "Offline", node);
 
         cib_node = pcmk__inject_node_state_change(cib, node, false);
         pcmk__assert(cib_node != NULL);
 
         rc = cib->cmds->modify(cib, PCMK_XE_STATUS, cib_node, cib_sync_call);
         pcmk__assert(rc == pcmk_ok);
         pcmk__xml_free(cib_node);
 
         xpath = crm_strdup_printf("//" PCMK__XE_NODE_STATE
                                   "[@" PCMK_XA_UNAME "='%s']"
                                   "/" PCMK__XE_LRM,
                                   node);
         cib->cmds->remove(cib, xpath, NULL, cib_xpath|cib_sync_call);
         free(xpath);
 
         xpath = crm_strdup_printf("//" PCMK__XE_NODE_STATE
                                   "[@" PCMK_XA_UNAME "='%s']"
                                   "/" PCMK__XE_TRANSIENT_ATTRIBUTES,
                                   node);
         cib->cmds->remove(cib, xpath, NULL, cib_xpath|cib_sync_call);
         free(xpath);
     }
 
     for (iter = injections->node_fail; iter != NULL; iter = iter->next) {
         const char *node = (const char *) iter->data;
 
         out->message(out, "inject-modify-node", "Failing", node);
 
         cib_node = pcmk__inject_node_state_change(cib, node, true);
         crm_xml_add(cib_node, PCMK__XA_IN_CCM, PCMK_VALUE_FALSE);
         pcmk__assert(cib_node != NULL);
 
         rc = cib->cmds->modify(cib, PCMK_XE_STATUS, cib_node, cib_sync_call);
         pcmk__assert(rc == pcmk_ok);
         pcmk__xml_free(cib_node);
     }
 
     for (iter = injections->ticket_grant; iter != NULL; iter = iter->next) {
         const char *ticket_id = (const char *) iter->data;
 
         out->message(out, "inject-modify-ticket", "Granting", ticket_id);
 
         rc = set_ticket_state_attr(out, ticket_id, PCMK__XA_GRANTED, true, cib);
         pcmk__assert(rc == pcmk_rc_ok);
     }
 
     for (iter = injections->ticket_revoke; iter != NULL; iter = iter->next) {
         const char *ticket_id = (const char *) iter->data;
 
         out->message(out, "inject-modify-ticket", "Revoking", ticket_id);
 
         rc = set_ticket_state_attr(out, ticket_id, PCMK__XA_GRANTED, false,
                                    cib);
         pcmk__assert(rc == pcmk_rc_ok);
     }
 
     for (iter = injections->ticket_standby; iter != NULL; iter = iter->next) {
         const char *ticket_id = (const char *) iter->data;
 
         out->message(out, "inject-modify-ticket", "Standby", ticket_id);
 
         rc = set_ticket_state_attr(out, ticket_id, PCMK_XA_STANDBY, true, cib);
         pcmk__assert(rc == pcmk_rc_ok);
     }
 
     for (iter = injections->ticket_activate; iter != NULL; iter = iter->next) {
         const char *ticket_id = (const char *) iter->data;
 
         out->message(out, "inject-modify-ticket", "Activating", ticket_id);
 
         rc = set_ticket_state_attr(out, ticket_id, PCMK_XA_STANDBY, false, cib);
         pcmk__assert(rc == pcmk_rc_ok);
     }
 
     for (iter = injections->op_inject; iter != NULL; iter = iter->next) {
         inject_action(out, (const char *) iter->data, cib, scheduler);
     }
 
     if (!out->is_quiet(out)) {
         out->end_list(out);
     }
 }
 
 void
 pcmk_free_injections(pcmk_injections_t *injections)
 {
     if (injections == NULL) {
         return;
     }
 
     g_list_free_full(injections->node_up, g_free);
     g_list_free_full(injections->node_down, g_free);
     g_list_free_full(injections->node_fail, g_free);
     g_list_free_full(injections->op_fail, g_free);
     g_list_free_full(injections->op_inject, g_free);
     g_list_free_full(injections->ticket_grant, g_free);
     g_list_free_full(injections->ticket_revoke, g_free);
     g_list_free_full(injections->ticket_standby, g_free);
     g_list_free_full(injections->ticket_activate, g_free);
     free(injections->quorum);
     free(injections->watchdog);
 
     free(injections);
 }
diff --git a/lib/pacemaker/pcmk_simulate.c b/lib/pacemaker/pcmk_simulate.c
index 7b3080d57e..dadf52c1e3 100644
--- a/lib/pacemaker/pcmk_simulate.c
+++ b/lib/pacemaker/pcmk_simulate.c
@@ -1,1006 +1,1017 @@
 /*
  * Copyright 2021-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 <crm/cib/internal.h>
 #include <crm/common/output.h>
 #include <crm/common/results.h>
 #include <crm/common/scheduler.h>
 #include <pacemaker-internal.h>
 #include <pacemaker.h>
 
 #include <stdint.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
 #include "libpacemaker_private.h"
 
 static pcmk__output_t *out = NULL;
 static cib_t *fake_cib = NULL;
 static GList *fake_resource_list = NULL;
 static const GList *fake_op_fail_list = NULL;
 
 static void set_effective_date(pcmk_scheduler_t *scheduler, bool print_original,
                                const char *use_date);
 
 /*!
  * \internal
  * \brief Create an action name for use in a dot graph
  *
  * \param[in] action   Action to create name for
  * \param[in] verbose  If true, add action ID to name
  *
  * \return Newly allocated string with action name
  * \note It is the caller's responsibility to free the result.
  */
 static char *
 create_action_name(const pcmk_action_t *action, bool verbose)
 {
     char *action_name = NULL;
     const char *prefix = "";
     const char *action_host = NULL;
     const char *history_id = NULL;
     const char *task = action->task;
 
     if (action->node != NULL) {
         action_host = action->node->priv->name;
     } else if (!pcmk_is_set(action->flags, pcmk__action_pseudo)) {
         action_host = "<none>";
     }
 
     if (pcmk__str_eq(action->task, PCMK_ACTION_CANCEL, pcmk__str_none)) {
         prefix = "Cancel ";
         task = action->cancel_task;
     }
 
     if (action->rsc != NULL) {
         history_id = action->rsc->priv->history_id;
     }
 
     if (history_id != NULL) {
         char *key = NULL;
         guint interval_ms = 0;
 
         if (pcmk__guint_from_hash(action->meta, PCMK_META_INTERVAL, 0,
                                   &interval_ms) != pcmk_rc_ok) {
             interval_ms = 0;
         }
 
         if (pcmk__strcase_any_of(action->task, PCMK_ACTION_NOTIFY,
                                  PCMK_ACTION_NOTIFIED, NULL)) {
             const char *n_type = g_hash_table_lookup(action->meta,
                                                      "notify_key_type");
             const char *n_task = g_hash_table_lookup(action->meta,
                                                      "notify_key_operation");
 
             pcmk__assert((n_type != NULL) && (n_task != NULL));
             key = pcmk__notify_key(history_id, n_type, n_task);
         } else {
             key = pcmk__op_key(history_id, task, interval_ms);
         }
 
         if (action_host != NULL) {
             action_name = crm_strdup_printf("%s%s %s",
                                             prefix, key, action_host);
         } else {
             action_name = crm_strdup_printf("%s%s", prefix, key);
         }
         free(key);
 
     } else if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH,
                             pcmk__str_none)) {
         const char *op = g_hash_table_lookup(action->meta,
                                              PCMK__META_STONITH_ACTION);
 
         action_name = crm_strdup_printf("%s%s '%s' %s",
                                         prefix, action->task, op, action_host);
 
     } else if (action->rsc && action_host) {
         action_name = crm_strdup_printf("%s%s %s",
                                         prefix, action->uuid, action_host);
 
     } else if (action_host) {
         action_name = crm_strdup_printf("%s%s %s",
                                         prefix, action->task, action_host);
 
     } else {
         action_name = crm_strdup_printf("%s", action->uuid);
     }
 
     if (verbose) {
         char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id);
 
         free(action_name);
         action_name = with_id;
     }
     return action_name;
 }
 
 /*!
  * \internal
  * \brief Display the status of a cluster
  *
  * \param[in,out] scheduler     Scheduler data
  * \param[in]     show_opts     How to modify display (as pcmk_show_opt_e flags)
  * \param[in]     section_opts  Sections to display (as pcmk_section_e flags)
  * \param[in]     title         What to use as list title
  * \param[in]     print_spacer  Whether to display a spacer first
  */
 static void
 print_cluster_status(pcmk_scheduler_t *scheduler, uint32_t show_opts,
                      uint32_t section_opts, const char *title,
                      bool print_spacer)
 {
     pcmk__output_t *out = scheduler->priv->out;
     GList *all = NULL;
     crm_exit_t stonith_rc = 0;
     enum pcmk_pacemakerd_state state = pcmk_pacemakerd_state_invalid;
 
     section_opts |= pcmk_section_nodes | pcmk_section_resources;
     show_opts |= pcmk_show_inactive_rscs | pcmk_show_failed_detail;
 
     all = g_list_prepend(all, (gpointer) "*");
 
     PCMK__OUTPUT_SPACER_IF(out, print_spacer);
     out->begin_list(out, NULL, NULL, "%s", title);
     out->message(out, "cluster-status",
                  scheduler, state, stonith_rc, NULL,
                  pcmk__fence_history_none, section_opts, show_opts, NULL,
                  all, all);
     out->end_list(out);
 
     g_list_free(all);
 }
 
 /*!
  * \internal
  * \brief Display a summary of all actions scheduled in a transition
  *
  * \param[in,out] scheduler     Scheduler data (fully scheduled)
  * \param[in]     print_spacer  Whether to display a spacer first
  */
 static void
 print_transition_summary(pcmk_scheduler_t *scheduler, bool print_spacer)
 {
     pcmk__output_t *out = scheduler->priv->out;
 
     PCMK__OUTPUT_SPACER_IF(out, print_spacer);
     out->begin_list(out, NULL, NULL, "Transition Summary");
     pcmk__output_actions(scheduler);
     out->end_list(out);
 }
 
 /*!
  * \internal
  * \brief Reset scheduler input, output, date, and flags
  *
  * \param[in,out] scheduler  Scheduler data
  * \param[in]     input      What to set as cluster input
  * \param[in]     out        What to set as cluster output object
  * \param[in]     use_date   What to set as cluster's current timestamp
  * \param[in]     flags      Group of enum pcmk__scheduler_flags to set
  */
 static void
 reset(pcmk_scheduler_t *scheduler, xmlNodePtr input, pcmk__output_t *out,
       const char *use_date, unsigned int flags)
 {
     scheduler->input = input;
     scheduler->priv->out = out;
     set_effective_date(scheduler, true, use_date);
     if (pcmk_is_set(flags, pcmk_sim_sanitized)) {
         pcmk__set_scheduler_flags(scheduler, pcmk__sched_sanitized);
     }
     if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
         pcmk__set_scheduler_flags(scheduler, pcmk__sched_output_scores);
     }
     if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
         pcmk__set_scheduler_flags(scheduler, pcmk__sched_show_utilization);
     }
 }
 
 /*!
  * \brief Write out a file in dot(1) format describing the actions that will
  *        be taken by the scheduler in response to an input CIB file.
  *
  * \param[in,out] scheduler    Scheduler data
  * \param[in]     dot_file     The filename to write
  * \param[in]     all_actions  Write all actions, even those that are optional
  *                             or are on unmanaged resources
  * \param[in]     verbose      Add extra information, such as action IDs, to the
  *                             output
  *
  * \return Standard Pacemaker return code
  */
 static int
 write_sim_dotfile(pcmk_scheduler_t *scheduler, const char *dot_file,
                   bool all_actions, bool verbose)
 {
     GList *iter = NULL;
     FILE *dot_strm = fopen(dot_file, "w");
 
     if (dot_strm == NULL) {
         return errno;
     }
 
     fprintf(dot_strm, " digraph \"g\" {\n");
     for (iter = scheduler->priv->actions; iter != NULL; iter = iter->next) {
         pcmk_action_t *action = (pcmk_action_t *) iter->data;
         const char *style = "dashed";
         const char *font = "black";
         const char *color = "black";
         char *action_name = create_action_name(action, verbose);
 
         if (pcmk_is_set(action->flags, pcmk__action_pseudo)) {
             font = "orange";
         }
 
         if (pcmk_is_set(action->flags, pcmk__action_added_to_graph)) {
             style = PCMK__VALUE_BOLD;
             color = "green";
 
         } else if ((action->rsc != NULL)
                    && !pcmk_is_set(action->rsc->flags, pcmk__rsc_managed)) {
             color = "red";
             font = "purple";
             if (!all_actions) {
                 goto do_not_write;
             }
 
         } else if (pcmk_is_set(action->flags, pcmk__action_optional)) {
             color = "blue";
             if (!all_actions) {
                 goto do_not_write;
             }
 
         } else {
             color = "red";
             CRM_LOG_ASSERT(!pcmk_is_set(action->flags, pcmk__action_runnable));
         }
 
         pcmk__set_action_flags(action, pcmk__action_added_to_graph);
         fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n",
                 action_name, style, color, font);
   do_not_write:
         free(action_name);
     }
 
     for (iter = scheduler->priv->actions; iter != NULL; iter = iter->next) {
         pcmk_action_t *action = (pcmk_action_t *) iter->data;
 
         for (GList *before_iter = action->actions_before;
              before_iter != NULL; before_iter = before_iter->next) {
 
             pcmk__related_action_t *before = before_iter->data;
 
             char *before_name = NULL;
             char *after_name = NULL;
             const char *style = "dashed";
             bool optional = true;
 
             if (before->graphed) {
                 optional = false;
                 style = PCMK__VALUE_BOLD;
             } else if (before->flags == pcmk__ar_none) {
                 continue;
             } else if (pcmk_is_set(before->action->flags,
                                    pcmk__action_added_to_graph)
                        && pcmk_is_set(action->flags, pcmk__action_added_to_graph)
                        && before->flags != pcmk__ar_if_on_same_node_or_target) {
                 optional = false;
             }
 
             if (all_actions || !optional) {
                 before_name = create_action_name(before->action, verbose);
                 after_name = create_action_name(action, verbose);
                 fprintf(dot_strm, "\"%s\" -> \"%s\" [ style = %s]\n",
                         before_name, after_name, style);
                 free(before_name);
                 free(after_name);
             }
         }
     }
 
     fprintf(dot_strm, "}\n");
     fflush(dot_strm);
     fclose(dot_strm);
     return pcmk_rc_ok;
 }
 
 /*!
  * \brief Profile the configuration updates and scheduler actions in a single
  *        CIB file, printing the profiling timings.
  *
  * \note \p scheduler->priv->out must have been set to a valid \p pcmk__output_t
  *       object before this function is called.
  *
  * \param[in]     xml_file   The CIB file to profile
  * \param[in]     repeat     Number of times to run
  * \param[in,out] scheduler  Scheduler data
  * \param[in]     use_date   The date to set the cluster's time to (may be NULL)
  */
 static void
 profile_file(const char *xml_file, long long repeat,
              pcmk_scheduler_t *scheduler, const char *use_date)
 {
     pcmk__output_t *out = scheduler->priv->out;
     xmlNode *cib_object = NULL;
     clock_t start = 0;
     clock_t end;
     unsigned long long scheduler_flags = pcmk__sched_none;
 
     pcmk__assert(out != NULL);
 
     cib_object = pcmk__xml_read(xml_file);
     start = clock();
 
     if (pcmk_find_cib_element(cib_object, PCMK_XE_STATUS) == NULL) {
         pcmk__xe_create(cib_object, PCMK_XE_STATUS);
     }
 
     if (pcmk__update_configured_schema(&cib_object, false) != pcmk_rc_ok) {
         pcmk__xml_free(cib_object);
         return;
     }
 
     if (!pcmk__validate_xml(cib_object, NULL, NULL, NULL)) {
         pcmk__xml_free(cib_object);
         return;
     }
 
     if (pcmk_is_set(scheduler->flags, pcmk__sched_output_scores)) {
         scheduler_flags |= pcmk__sched_output_scores;
     }
     if (pcmk_is_set(scheduler->flags, pcmk__sched_show_utilization)) {
         scheduler_flags |= pcmk__sched_show_utilization;
     }
 
     for (int i = 0; i < repeat; ++i) {
         xmlNode *input = cib_object;
 
         if (repeat > 1) {
             input = pcmk__xml_copy(NULL, cib_object);
         }
         scheduler->input = input;
         set_effective_date(scheduler, false, use_date);
         pcmk__schedule_actions(input, scheduler_flags, scheduler);
         pe_reset_working_set(scheduler);
     }
 
     end = clock();
     out->message(out, "profile", xml_file, start, end);
 }
 
 void
 pcmk__profile_dir(const char *dir, long long repeat,
                   pcmk_scheduler_t *scheduler, const char *use_date)
 {
     pcmk__output_t *out = scheduler->priv->out;
     struct dirent **namelist;
 
     int file_num = scandir(dir, &namelist, 0, alphasort);
 
     pcmk__assert(out != NULL);
 
     if (file_num > 0) {
         struct stat prop;
         char buffer[FILENAME_MAX];
 
         out->begin_list(out, NULL, NULL, "Timings");
 
         while (file_num--) {
             if ('.' == namelist[file_num]->d_name[0]) {
                 free(namelist[file_num]);
                 continue;
 
             } else if (!pcmk__ends_with_ext(namelist[file_num]->d_name,
                                             ".xml")) {
                 free(namelist[file_num]);
                 continue;
             }
             snprintf(buffer, sizeof(buffer), "%s/%s",
                      dir, namelist[file_num]->d_name);
             if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) {
                 profile_file(buffer, repeat, scheduler, use_date);
             }
             free(namelist[file_num]);
         }
         free(namelist);
 
         out->end_list(out);
     }
 }
 
 /*!
  * \brief Set the date of the cluster, either to the value given by
  *        \p use_date, or to the \c PCMK_XA_EXECUTION_DATE value in the CIB.
  *
  * \note \p scheduler->priv->out must have been set to a valid \p pcmk__output_t
  *       object before this function is called.
  *
  * \param[in,out] scheduler       Scheduler data
  * \param[in]     print_original  If \p true, the \c PCMK_XA_EXECUTION_DATE
  *                                should also be printed
  * \param[in]     use_date        The date to set the cluster's time to
  *                                (may be NULL)
  */
 static void
 set_effective_date(pcmk_scheduler_t *scheduler, bool print_original,
                    const char *use_date)
 {
     pcmk__output_t *out = scheduler->priv->out;
     time_t original_date = 0;
 
     pcmk__assert(out != NULL);
 
     crm_element_value_epoch(scheduler->input, PCMK_XA_EXECUTION_DATE,
                             &original_date);
 
     if (use_date) {
         scheduler->priv->now = crm_time_new(use_date);
         out->info(out, "Setting effective cluster time: %s", use_date);
         crm_time_log(LOG_NOTICE, "Pretending 'now' is", scheduler->priv->now,
                      crm_time_log_date | crm_time_log_timeofday);
 
     } else if (original_date != 0) {
         scheduler->priv->now = pcmk__copy_timet(original_date);
 
         if (print_original) {
             char *when = crm_time_as_string(scheduler->priv->now,
                                             crm_time_log_date
                                             |crm_time_log_timeofday);
 
             out->info(out, "Using the original execution date of: %s", when);
             free(when);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Simulate successfully executing a pseudo-action in a graph
  *
  * \param[in,out] graph   Graph to update with pseudo-action result
  * \param[in,out] action  Pseudo-action to simulate executing
  *
  * \return Standard Pacemaker return code
  */
 static int
 simulate_pseudo_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     const char *node = crm_element_value(action->xml, PCMK__META_ON_NODE);
     const char *task = crm_element_value(action->xml, PCMK__XA_OPERATION_KEY);
 
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     out->message(out, "inject-pseudo-action", node, task);
 
     pcmk__update_graph(graph, action);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Simulate executing a resource action in a graph
  *
  * \param[in,out] graph   Graph to update with resource action result
  * \param[in,out] action  Resource action to simulate executing
  *
  * \return Standard Pacemaker return code
  */
 static int
 simulate_resource_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     int rc;
     lrmd_event_data_t *op = NULL;
     int target_outcome = PCMK_OCF_OK;
 
     const char *rtype = NULL;
     const char *rclass = NULL;
     const char *resource = NULL;
     const char *rprovider = NULL;
     const char *resource_config_name = NULL;
     const char *operation = crm_element_value(action->xml, PCMK_XA_OPERATION);
     const char *target_rc_s = crm_meta_value(action->params,
                                              PCMK__META_OP_TARGET_RC);
 
     xmlNode *cib_node = NULL;
     xmlNode *cib_resource = NULL;
     xmlNode *action_rsc = pcmk__xe_first_child(action->xml, PCMK_XE_PRIMITIVE,
                                                NULL, NULL);
 
     char *node = crm_element_value_copy(action->xml, PCMK__META_ON_NODE);
     char *uuid = NULL;
     const char *router_node = crm_element_value(action->xml,
                                                 PCMK__XA_ROUTER_NODE);
 
     // Certain actions don't need to be displayed or history entries
     if (pcmk__str_eq(operation, CRM_OP_REPROBE, pcmk__str_none)) {
         crm_debug("No history injection for %s op on %s", operation, node);
         goto done; // Confirm action and update graph
     }
 
     if (action_rsc == NULL) { // Shouldn't be possible
         crm_log_xml_err(action->xml, "Bad");
         free(node);
         return EPROTO;
     }
 
     /* A resource might be known by different names in the configuration and in
      * the action (for example, a clone instance). Grab the configuration name
      * (which is preferred when writing history), and if necessary, the instance
      * name.
      */
     resource_config_name = crm_element_value(action_rsc, PCMK_XA_ID);
     if (resource_config_name == NULL) { // Shouldn't be possible
         crm_log_xml_err(action->xml, "No ID");
         free(node);
         return EPROTO;
     }
     resource = resource_config_name;
     if (pe_find_resource(fake_resource_list, resource) == NULL) {
         const char *longname = crm_element_value(action_rsc, PCMK__XA_LONG_ID);
 
         if ((longname != NULL)
             && (pe_find_resource(fake_resource_list, longname) != NULL)) {
             resource = longname;
         }
     }
 
     // Certain actions need to be displayed but don't need history entries
     if (pcmk__strcase_any_of(operation, PCMK_ACTION_DELETE,
                              PCMK_ACTION_META_DATA, NULL)) {
         out->message(out, "inject-rsc-action", resource, operation, node,
                      (guint) 0);
         goto done; // Confirm action and update graph
     }
 
     rclass = crm_element_value(action_rsc, PCMK_XA_CLASS);
     rtype = crm_element_value(action_rsc, PCMK_XA_TYPE);
     rprovider = crm_element_value(action_rsc, PCMK_XA_PROVIDER);
 
     pcmk__scan_min_int(target_rc_s, &target_outcome, 0);
 
     pcmk__assert(fake_cib->cmds->query(fake_cib, NULL, NULL,
                                        cib_sync_call) == pcmk_ok);
 
     // Ensure the action node is in the CIB
     uuid = crm_element_value_copy(action->xml, PCMK__META_ON_NODE_UUID);
     cib_node = pcmk__inject_node(fake_cib, node,
                                  ((router_node == NULL)? uuid: node));
     free(uuid);
     pcmk__assert(cib_node != NULL);
 
     // Add a history entry for the action
     cib_resource = pcmk__inject_resource_history(out, cib_node, resource,
                                                  resource_config_name,
                                                  rclass, rtype, rprovider);
     if (cib_resource == NULL) {
         crm_err("Could not simulate action %d history for resource %s",
                 action->id, resource);
         free(node);
         pcmk__xml_free(cib_node);
         return EINVAL;
     }
 
     // Simulate and display an executor event for the action result
     op = pcmk__event_from_graph_action(cib_resource, action, PCMK_EXEC_DONE,
                                        target_outcome, "User-injected result");
     out->message(out, "inject-rsc-action", resource, op->op_type, node,
                  op->interval_ms);
 
     // Check whether action is in a list of desired simulated failures
     for (const GList *iter = fake_op_fail_list;
          iter != NULL; iter = iter->next) {
         const char *spec = (const char *) iter->data;
         char *key = NULL;
         const char *match_name = NULL;
+        const char *offset = NULL;
 
         // Allow user to specify anonymous clone with or without instance number
         key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource, op->op_type,
                                 op->interval_ms, node);
         if (strncasecmp(key, spec, strlen(key)) == 0) {
             match_name = resource;
         }
         free(key);
 
         // If not found, try the resource's name in the configuration
         if ((match_name == NULL)
             && (strcmp(resource, resource_config_name) != 0)) {
 
             key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource_config_name,
                                     op->op_type, op->interval_ms, node);
             if (strncasecmp(key, spec, strlen(key)) == 0) {
                 match_name = resource_config_name;
             }
             free(key);
         }
 
         if (match_name == NULL) {
             continue; // This failed action entry doesn't match
         }
 
         // ${match_name}_${task}_${interval_in_ms}@${node}=${rc}
         rc = sscanf(spec, "%*[^=]=%d", (int *) &op->rc);
         if (rc != 1) {
             out->err(out, "Invalid failed operation '%s' "
                           "(result code must be integer)", spec);
             continue; // Keep checking other list entries
         }
 
         out->info(out, "Pretending action %d failed with rc=%d",
                   action->id, op->rc);
         pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
         graph->abort_priority = PCMK_SCORE_INFINITY;
+
+        if (pcmk__str_eq(op->op_type, PCMK_ACTION_START, pcmk__str_none)) {
+            offset = pcmk__s(graph->failed_start_offset, PCMK_VALUE_INFINITY);
+
+        } else if (pcmk__str_eq(op->op_type, PCMK_ACTION_STOP,
+                                pcmk__str_none)) {
+            offset = pcmk__s(graph->failed_stop_offset, PCMK_VALUE_INFINITY);
+        }
+
         pcmk__inject_failcount(out, fake_cib, cib_node, match_name, op->op_type,
-                               op->interval_ms, op->rc);
+                               op->interval_ms, op->rc,
+                               pcmk_str_is_infinity(offset));
         break;
     }
 
     pcmk__inject_action_result(cib_resource, op, node, target_outcome);
     lrmd_free_event(op);
     rc = fake_cib->cmds->modify(fake_cib, PCMK_XE_STATUS, cib_node,
                                 cib_sync_call);
     pcmk__assert(rc == pcmk_ok);
 
   done:
     free(node);
     pcmk__xml_free(cib_node);
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     pcmk__update_graph(graph, action);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Simulate successfully executing a cluster action
  *
  * \param[in,out] graph   Graph to update with action result
  * \param[in,out] action  Cluster action to simulate
  *
  * \return Standard Pacemaker return code
  */
 static int
 simulate_cluster_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     const char *node = crm_element_value(action->xml, PCMK__META_ON_NODE);
     const char *task = crm_element_value(action->xml, PCMK_XA_OPERATION);
     xmlNode *rsc = pcmk__xe_first_child(action->xml, PCMK_XE_PRIMITIVE, NULL,
                                         NULL);
 
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     out->message(out, "inject-cluster-action", node, task, rsc);
     pcmk__update_graph(graph, action);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Simulate successfully executing a fencing action
  *
  * \param[in,out] graph   Graph to update with action result
  * \param[in,out] action  Fencing action to simulate
  *
  * \return Standard Pacemaker return code
  */
 static int
 simulate_fencing_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     const char *op = crm_meta_value(action->params, PCMK__META_STONITH_ACTION);
     char *target = crm_element_value_copy(action->xml, PCMK__META_ON_NODE);
 
     out->message(out, "inject-fencing-action", target, op);
 
     if (!pcmk__str_eq(op, PCMK_ACTION_ON, pcmk__str_casei)) {
         int rc = pcmk_ok;
         GString *xpath = g_string_sized_new(512);
 
         // Set node state to offline
         xmlNode *cib_node = pcmk__inject_node_state_change(fake_cib, target,
                                                            false);
 
         pcmk__assert(cib_node != NULL);
         crm_xml_add(cib_node, PCMK_XA_CRM_DEBUG_ORIGIN, __func__);
         rc = fake_cib->cmds->replace(fake_cib, PCMK_XE_STATUS, cib_node,
                                      cib_sync_call);
         pcmk__assert(rc == pcmk_ok);
 
         // Simulate controller clearing node's resource history and attributes
         pcmk__g_strcat(xpath,
                        "//" PCMK__XE_NODE_STATE
                        "[@" PCMK_XA_UNAME "='", target, "']/" PCMK__XE_LRM,
                        NULL);
         fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
                                cib_xpath|cib_sync_call);
 
         g_string_truncate(xpath, 0);
         pcmk__g_strcat(xpath,
                        "//" PCMK__XE_NODE_STATE
                        "[@" PCMK_XA_UNAME "='", target, "']"
                        "/" PCMK__XE_TRANSIENT_ATTRIBUTES, NULL);
         fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
                                cib_xpath|cib_sync_call);
 
         pcmk__xml_free(cib_node);
         g_string_free(xpath, TRUE);
     }
 
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     pcmk__update_graph(graph, action);
     free(target);
     return pcmk_rc_ok;
 }
 
 enum pcmk__graph_status
 pcmk__simulate_transition(pcmk_scheduler_t *scheduler, cib_t *cib,
                           const GList *op_fail_list)
 {
     pcmk__graph_t *transition = NULL;
     enum pcmk__graph_status graph_rc;
 
     pcmk__graph_functions_t simulation_fns = {
         simulate_pseudo_action,
         simulate_resource_action,
         simulate_cluster_action,
         simulate_fencing_action,
     };
 
     out = scheduler->priv->out;
 
     fake_cib = cib;
     fake_op_fail_list = op_fail_list;
 
     if (!out->is_quiet(out)) {
         out->begin_list(out, NULL, NULL, "Executing Cluster Transition");
     }
 
     pcmk__set_graph_functions(&simulation_fns);
     transition = pcmk__unpack_graph(scheduler->priv->graph, crm_system_name);
     pcmk__log_graph(LOG_DEBUG, transition);
 
     fake_resource_list = scheduler->priv->resources;
     do {
         graph_rc = pcmk__execute_graph(transition);
     } while (graph_rc == pcmk__graph_active);
     fake_resource_list = NULL;
 
     if (graph_rc != pcmk__graph_complete) {
         out->err(out, "Transition failed: %s",
                  pcmk__graph_status2text(graph_rc));
         pcmk__log_graph(LOG_ERR, transition);
         out->err(out, "An invalid transition was produced");
     }
     pcmk__free_graph(transition);
 
     if (!out->is_quiet(out)) {
         // If not quiet, we'll need the resulting CIB for later display
         xmlNode *cib_object = NULL;
         int rc = fake_cib->cmds->query(fake_cib, NULL, &cib_object,
                                        cib_sync_call);
 
         pcmk__assert(rc == pcmk_ok);
         pe_reset_working_set(scheduler);
         scheduler->input = cib_object;
         out->end_list(out);
     }
     return graph_rc;
 }
 
 int
 pcmk__simulate(pcmk_scheduler_t *scheduler, pcmk__output_t *out,
                const pcmk_injections_t *injections, unsigned int flags,
                uint32_t section_opts, const char *use_date,
                const char *input_file, const char *graph_file,
                const char *dot_file)
 {
     int printed = pcmk_rc_no_output;
     int rc = pcmk_rc_ok;
     xmlNodePtr input = NULL;
     cib_t *cib = NULL;
 
     rc = cib__signon_query(out, &cib, &input);
     if (rc != pcmk_rc_ok) {
         goto simulate_done;
     }
 
     reset(scheduler, input, out, use_date, flags);
     cluster_status(scheduler);
 
     if (!out->is_quiet(out)) {
         const bool show_pending = pcmk_is_set(flags, pcmk_sim_show_pending);
 
         if (pcmk_is_set(scheduler->flags, pcmk__sched_in_maintenance)) {
             printed = out->message(out, "maint-mode", scheduler->flags);
         }
 
         if ((scheduler->priv->disabled_resources > 0)
             || (scheduler->priv->blocked_resources > 0)) {
 
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             printed = out->info(out,
                                 "%d of %d resource instances DISABLED and "
                                 "%d BLOCKED from further action due to failure",
                                 scheduler->priv->disabled_resources,
                                 scheduler->priv->ninstances,
                                 scheduler->priv->blocked_resources);
         }
 
         /* Most formatted output headers use caps for each word, but this one
          * only has the first word capitalized for compatibility with pcs.
          */
         print_cluster_status(scheduler, (show_pending? pcmk_show_pending : 0),
                              section_opts, "Current cluster status",
                              (printed == pcmk_rc_ok));
         printed = pcmk_rc_ok;
     }
 
     // If the user requested any injections, handle them
     if ((injections->node_down != NULL)
         || (injections->node_fail != NULL)
         || (injections->node_up != NULL)
         || (injections->op_inject != NULL)
         || (injections->ticket_activate != NULL)
         || (injections->ticket_grant != NULL)
         || (injections->ticket_revoke != NULL)
         || (injections->ticket_standby != NULL)
         || (injections->watchdog != NULL)) {
 
         PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
         pcmk__inject_scheduler_input(scheduler, cib, injections);
         printed = pcmk_rc_ok;
 
         rc = cib->cmds->query(cib, NULL, &input, cib_sync_call);
         if (rc != pcmk_rc_ok) {
             rc = pcmk_legacy2rc(rc);
             goto simulate_done;
         }
 
         cleanup_calculations(scheduler);
         reset(scheduler, input, out, use_date, flags);
         cluster_status(scheduler);
     }
 
     if (input_file != NULL) {
         rc = pcmk__xml_write_file(input, input_file, false);
         if (rc != pcmk_rc_ok) {
             goto simulate_done;
         }
     }
 
     if (pcmk_any_flags_set(flags, pcmk_sim_process | pcmk_sim_simulate)) {
         pcmk__output_t *logger_out = NULL;
         unsigned long long scheduler_flags = pcmk__sched_none;
 
         if (pcmk_is_set(scheduler->flags, pcmk__sched_output_scores)) {
             scheduler_flags |= pcmk__sched_output_scores;
         }
         if (pcmk_is_set(scheduler->flags, pcmk__sched_show_utilization)) {
             scheduler_flags |= pcmk__sched_show_utilization;
         }
 
         if (pcmk_all_flags_set(scheduler->flags,
                                pcmk__sched_output_scores
                                |pcmk__sched_show_utilization)) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL,
                             "Assignment Scores and Utilization Information");
             printed = pcmk_rc_ok;
 
         } else if (pcmk_is_set(scheduler->flags, pcmk__sched_output_scores)) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL, "Assignment Scores");
             printed = pcmk_rc_ok;
 
         } else if (pcmk_is_set(scheduler->flags, pcmk__sched_show_utilization)) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL, "Utilization Information");
             printed = pcmk_rc_ok;
 
         } else {
             rc = pcmk__log_output_new(&logger_out);
             if (rc != pcmk_rc_ok) {
                 goto simulate_done;
             }
             pe__register_messages(logger_out);
             pcmk__register_lib_messages(logger_out);
             scheduler->priv->out = logger_out;
         }
 
         pcmk__schedule_actions(input, scheduler_flags, scheduler);
 
         if (logger_out == NULL) {
             out->end_list(out);
         } else {
             logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
             pcmk__output_free(logger_out);
             scheduler->priv->out = out;
         }
 
         input = NULL;           /* Don't try and free it twice */
 
         if (graph_file != NULL) {
             rc = pcmk__xml_write_file(scheduler->priv->graph, graph_file,
                                       false);
             if (rc != pcmk_rc_ok) {
                 rc = pcmk_rc_graph_error;
                 goto simulate_done;
             }
         }
 
         if (dot_file != NULL) {
             rc = write_sim_dotfile(scheduler, dot_file,
                                    pcmk_is_set(flags, pcmk_sim_all_actions),
                                    pcmk_is_set(flags, pcmk_sim_verbose));
             if (rc != pcmk_rc_ok) {
                 rc = pcmk_rc_dot_error;
                 goto simulate_done;
             }
         }
 
         if (!out->is_quiet(out)) {
             print_transition_summary(scheduler, printed == pcmk_rc_ok);
         }
     }
 
     rc = pcmk_rc_ok;
 
     if (!pcmk_is_set(flags, pcmk_sim_simulate)) {
         goto simulate_done;
     }
 
     PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
     if (pcmk__simulate_transition(scheduler, cib, injections->op_fail)
             != pcmk__graph_complete) {
         rc = pcmk_rc_invalid_transition;
     }
 
     if (out->is_quiet(out)) {
         goto simulate_done;
     }
 
     set_effective_date(scheduler, true, use_date);
 
     if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
         pcmk__set_scheduler_flags(scheduler, pcmk__sched_output_scores);
     }
     if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
         pcmk__set_scheduler_flags(scheduler, pcmk__sched_show_utilization);
     }
 
     cluster_status(scheduler);
     print_cluster_status(scheduler, 0, section_opts, "Revised Cluster Status",
                          true);
 
 simulate_done:
     cib__clean_up_connection(&cib);
     return rc;
 }
 
 int
 pcmk_simulate(xmlNodePtr *xml, pcmk_scheduler_t *scheduler,
               const pcmk_injections_t *injections, unsigned int flags,
               unsigned int section_opts, const char *use_date,
               const char *input_file, const char *graph_file,
               const char *dot_file)
 {
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__xml_output_new(&out, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     pe__register_messages(out);
     pcmk__register_lib_messages(out);
 
     rc = pcmk__simulate(scheduler, out, injections, flags, section_opts,
                         use_date, input_file, graph_file, dot_file);
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
     return rc;
 }