diff --git a/include/crm/common/output_internal.h b/include/crm/common/output_internal.h
index 0436cdef51..ba9c423eb4 100644
--- a/include/crm/common/output_internal.h
+++ b/include/crm/common/output_internal.h
@@ -1,880 +1,880 @@
 /*
  * Copyright 2019-2021 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__OUTPUT_INTERNAL__H
 #  define PCMK__OUTPUT_INTERNAL__H
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /**
  * \file
  * \brief Formatted output for pacemaker tools
  */
 
 #  include <stdbool.h>
 #  include <stdio.h>
 #  include <libxml/tree.h>
 #  include <libxml/HTMLtree.h>
 
 #  include <glib.h>
 #  include <crm/common/results.h>
 
-#  define PCMK__API_VERSION "2.11"
+#  define PCMK__API_VERSION "2.12"
 
 #if defined(PCMK__WITH_ATTRIBUTE_OUTPUT_ARGS)
 #  define PCMK__OUTPUT_ARGS(ARGS...) __attribute__((output_args(ARGS)))
 #else
 #  define PCMK__OUTPUT_ARGS(ARGS...)
 #endif
 
 typedef struct pcmk__output_s pcmk__output_t;
 
 /*!
  * \internal
  * \brief The type of a function that creates a ::pcmk__output_t.
  *
  * Instances of this type are passed to pcmk__register_format(), stored in an
  * internal data structure, and later accessed by pcmk__output_new().  For 
  * examples, see pcmk__mk_xml_output() and pcmk__mk_text_output().
  *
  * \param[in] argv The list of command line arguments.
  */
 typedef pcmk__output_t * (*pcmk__output_factory_t)(char **argv);
 
 /*!
  * \internal
  * \brief The type of a custom message formatting function.
  *
  * These functions are defined by various libraries to support formatting of
  * types aside from the basic types provided by a ::pcmk__output_t.
  *
  * The meaning of the return value will be different for each message.
  * In general, however, 0 should be returned on success and a positive value
  * on error.
  *
  * \note These functions must not call va_start or va_end - that is done
  *       automatically before the custom formatting function is called.
  */
 typedef int (*pcmk__message_fn_t)(pcmk__output_t *out, va_list args);
 
 /*!
  * \internal
  * \brief Internal type for tracking custom messages.
  *
  * Each library can register functions that format custom message types.  These
  * are commonly used to handle some library-specific type.  Registration is
  * done by first defining a table of ::pcmk__message_entry_t structures and
  * then passing that table to pcmk__register_messages().  Separate handlers
  * can be defined for the same message, but for different formats (xml vs.
  * text).  Unknown formats will be ignored.
  *
  * Additionally, a "default" value for fmt_table can be used.  In this case,
  * fn will be registered for all supported formats.  It is also possible to
  * register a default and then override that registration with a format-specific
  * function if necessary.
  *
  * \note The ::pcmk__message_entry_t table is processed in one pass, in order,
  * from top to bottom.  This means later entries with the same message_id will
  * override previous ones.  Thus, any default entry must come before any
  * format-specific entries for the same message_id.
  */
 typedef struct pcmk__message_entry_s {
     /*!
      * \brief The message to be handled.
      *
      * This must be the same ID that is passed to the message function of
      * a ::pcmk__output_t.  Unknown message IDs will be ignored.
      */
     const char *message_id;
 
     /*!
      * \brief The format type this handler is for.
      *
      * This name must match the fmt_name of the currently active formatter in
      * order for the registered function to be called.  It is valid to have
      * multiple entries for the same message_id but with different fmt_name
      * values.
      */
     const char *fmt_name;
 
     /*!
      * \brief The function to be called for message_id given a match on
      *        fmt_name.  See comments on ::pcmk__message_fn_t.
      */
     pcmk__message_fn_t fn;
 } pcmk__message_entry_t;
 
 /*!
  * \internal
  * \brief This structure contains everything needed to add support for a
  *        single output formatter to a command line program.
  */
 typedef struct pcmk__supported_format_s {
     /*!
      * \brief The name of this output formatter, which should match the
      *        fmt_name parameter in some ::pcmk__output_t structure.
      */
     const char *name;
 
     /*!
      * \brief A function that creates a ::pcmk__output_t.
      */
     pcmk__output_factory_t create;
 
     /*!
      * \brief Format-specific command line options.  This can be NULL if
      *        no command line options should be supported.
      */
     GOptionEntry *options;
 } pcmk__supported_format_t;
 
 /* The following three blocks need to be updated each time a new base formatter
  * is added.
  */
 
 extern GOptionEntry pcmk__html_output_entries[];
 extern GOptionEntry pcmk__log_output_entries[];
 extern GOptionEntry pcmk__none_output_entries[];
 extern GOptionEntry pcmk__text_output_entries[];
 extern GOptionEntry pcmk__xml_output_entries[];
 
 pcmk__output_t *pcmk__mk_html_output(char **argv);
 pcmk__output_t *pcmk__mk_log_output(char **argv);
 pcmk__output_t *pcmk__mk_none_output(char **argv);
 pcmk__output_t *pcmk__mk_text_output(char **argv);
 pcmk__output_t *pcmk__mk_xml_output(char **argv);
 
 #define PCMK__SUPPORTED_FORMAT_HTML { "html", pcmk__mk_html_output, pcmk__html_output_entries }
 #define PCMK__SUPPORTED_FORMAT_LOG  { "log", pcmk__mk_log_output, pcmk__log_output_entries }
 #define PCMK__SUPPORTED_FORMAT_NONE { "none", pcmk__mk_none_output, pcmk__none_output_entries }
 #define PCMK__SUPPORTED_FORMAT_TEXT { "text", pcmk__mk_text_output, pcmk__text_output_entries }
 #define PCMK__SUPPORTED_FORMAT_XML  { "xml", pcmk__mk_xml_output, pcmk__xml_output_entries }
 
 /*!
  * \brief This structure contains everything that makes up a single output
  *        formatter.
  *
  * Instances of this structure may be created by calling pcmk__output_new()
  * with the name of the desired formatter.  They should later be freed with
  * pcmk__output_free().
  */
 struct pcmk__output_s {
     /*!
      * \brief The name of this output formatter.
      */
     const char *fmt_name;
 
     /*!
      * \brief Should this formatter supress most output?
      *
      * \note This setting is not respected by all formatters.  In general,
      *       machine-readable output formats will not support this while
      *       user-oriented formats will.  Callers should use is_quiet()
      *       to test whether to print or not.
      */
     bool quiet;
 
     /*!
      * \brief A copy of the request that generated this output.
      *
      * In the case of command line usage, this would be the command line
      * arguments.  For other use cases, it could be different.
      */
     gchar *request;
 
     /*!
      * \brief Where output should be written.
      *
      * This could be a file handle, or stdout or stderr.  This is really only
      * useful internally.
      */
     FILE *dest;
 
     /*!
      * \brief Custom messages that are currently registered on this formatter.
      *
      * Keys are the string message IDs, values are ::pcmk__message_fn_t function
      * pointers.
      */
     GHashTable *messages;
 
     /*!
      * \brief Implementation-specific private data.
      *
      * Each individual formatter may have some private data useful in its
      * implementation.  This points to that data.  Callers should not rely on
      * its contents or structure.
      */
     void *priv;
 
     /*!
      * \internal
      * \brief Take whatever actions are necessary to prepare out for use.  This is
      *        called by pcmk__output_new().  End users should not need to call this.
      *
      * \note For formatted output implementers - This function should be written in
      *       such a way that it can be called repeatedly on an already initialized
      *       object without causing problems, or on a previously finished object
      *       without crashing.
      *
      * \param[in,out] out The output functions structure.
      *
      * \return true on success, false on error.
      */
     bool (*init) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Free the private formatter-specific data.
      *
      * This is called from pcmk__output_free() and does not typically need to be
      * called directly.
      *
      * \param[in,out] out The output functions structure.
      */
     void (*free_priv) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Take whatever actions are necessary to end formatted output.
      *
      * This could include flushing output to a file, but does not include freeing
      * anything.  The finish method can potentially be fairly complicated, adding
      * additional information to the internal data structures or doing whatever
      * else.  It is therefore suggested that finish only be called once.
      *
      * \note The print parameter will only affect those formatters that do all
      *       their output at the end.  Console-oriented formatters typically print
      *       a line at a time as they go, so this parameter will not affect them.
      *       Structured formatters will honor it, however.
      *
      * \note The copy_dest parameter does not apply to all formatters.  Console-
      *       oriented formatters do not build up a structure as they go, and thus
      *       do not have anything to return.  Structured formatters will honor it,
      *       however.  Note that each type of formatter will return a different
      *       type of value in this parameter.  To use this parameter, call this
      *       function like so:
      *
      * \code
      * xmlNode *dest = NULL;
      * out->finish(out, exit_code, false, (void **) &dest);
      * \endcode
      *
      * \param[in,out] out         The output functions structure.
      * \param[in]     exit_status The exit value of the whole program.
      * \param[in]     print       Whether this function should write any output.
      * \param[out]    copy_dest   A destination to store a copy of the internal
      *                            data structure for this output, or NULL if no
      *                            copy is required.  The caller should free this
      *                            memory when done with it.
      */
     void (*finish) (pcmk__output_t *out, crm_exit_t exit_status, bool print,
                     void **copy_dest);
 
     /*!
      * \internal
      * \brief Finalize output and then immediately set back up to start a new set
      *        of output.
      *
      * This is conceptually the same as calling finish and then init, though in
      * practice more be happening behind the scenes.
      *
      * \note This function differs from finish in that no exit_status is added.
      *       The idea is that the program is not shutting down, so there is not
      *       yet a final exit code.  Call finish on the last time through if this
      *       is needed.
      *
      * \param[in,out] out The output functions structure.
      */
     void (*reset) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Register a custom message.
      *
      * \param[in,out] out        The output functions structure.
      * \param[in]     message_id The name of the message to register.  This name
      *                           will be used as the message_id parameter to the
      *                           message function in order to call the custom
      *                           format function.
      * \param[in]     fn         The custom format function to call for message_id.
      */
     void (*register_message) (pcmk__output_t *out, const char *message_id,
                               pcmk__message_fn_t fn);
 
     /*!
      * \internal
      * \brief Call a previously registered custom message.
      *
      * \param[in,out] out        The output functions structure.
      * \param[in]     message_id The name of the message to call.  This name must
      *                           be the same as the message_id parameter of some
      *                           previous call to register_message.
      * \param[in] ...            Arguments to be passed to the registered function.
      *
      * \return A standard Pacemaker return code.  Generally: 0 if a function was
      *         registered for the message, that function was called, and returned
      *         successfully; EINVAL if no function was registered; or pcmk_rc_no_output
      *         if a function was called but produced no output.
      */
     int (*message) (pcmk__output_t *out, const char *message_id, ...);
 
     /*!
      * \internal
      * \brief Format the output of a completed subprocess.
      *
      * \param[in,out] out         The output functions structure.
      * \param[in]     exit_status The exit value of the subprocess.
      * \param[in]     proc_stdout stdout from the completed subprocess.
      * \param[in]     proc_stderr stderr from the completed subprocess.
      */
     void (*subprocess_output) (pcmk__output_t *out, int exit_status,
                                const char *proc_stdout, const char *proc_stderr);
 
     /*!
      * \internal
      * \brief Format version information.  This is useful for the --version
      *        argument of command line tools.
      *
      * \param[in,out] out      The output functions structure.
      * \param[in]     extended Add additional version information.
      */
     void (*version) (pcmk__output_t *out, bool extended);
 
     /*!
      * \internal
      * \brief Format an informational message that should be shown to
      *        to an interactive user.  Not all formatters will do this.
      *
      * \note A newline will automatically be added to the end of the format
      *       string, so callers should not include a newline.
      *
      * \param[in,out] out The output functions structure.
      * \param[in]     buf The message to be printed.
      * \param[in]     ... Arguments to be formatted.
      *
      * \return A standard Pacemaker return code.  Generally: pcmk_rc_ok
      *         if output was produced and pcmk_rc_no_output if it was not.
      *         As not all formatters implement this function, those that
      *         do not will always just return pcmk_rc_no_output.
      */
     int (*info) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
 
     /*!
      * \internal
      * \brief Format an error message that should be shown to an interactive
      *        user.  Not all formatters will do this.
      *
      * \note A newline will automatically be added to the end of the format
      *       string, so callers should not include a newline.
      *
      * \param[in,out] out The output functions structure.
      * \param[in]     buf The message to be printed.
      * \param[in]     ... Arguments to be formatted.
      */
     void (*err) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
 
     /*!
      * \internal
      * \brief Format already formatted XML.
      *
      * \param[in,out] out  The output functions structure.
      * \param[in]     name A name to associate with the XML.
      * \param[in]     buf  The XML in a string.
      */
     void (*output_xml) (pcmk__output_t *out, const char *name, const char *buf);
 
     /*!
      * \internal
      * \brief Start a new list of items.
      *
      * \note For text output, this corresponds to another level of indentation.  For
      *       XML output, this corresponds to wrapping any following output in another
      *       layer of tags.
      *
      * \note If singular_noun and plural_noun are non-NULL, calling end_list will
      *       result in a summary being added.
      *
      * \param[in,out] out           The output functions structure.
      * \param[in]     singular_noun When outputting the summary for a list with
      *                              one item, the noun to use.
      * \param[in]     plural_noun   When outputting the summary for a list with
      *                              more than one item, the noun to use.
      * \param[in]     format        The format string.
      * \param[in]     ...           Arguments to be formatted.
      */
     void (*begin_list) (pcmk__output_t *out, const char *singular_noun,
                         const char *plural_noun, const char *format, ...)
                         G_GNUC_PRINTF(4, 5);
 
     /*!
      * \internal
      * \brief Format a single item in a list.
      *
      * \param[in,out] out     The output functions structure.
      * \param[in]     name    A name to associate with this item.
      * \param[in]     format  The format string.
      * \param[in]     ...     Arguments to be formatted.
      */
     void (*list_item) (pcmk__output_t *out, const char *name, const char *format, ...)
                       G_GNUC_PRINTF(3, 4);
 
     /*!
      * \internal
      * \brief Increment the internal counter of the current list's length.
      *
      * Typically, this counter is maintained behind the scenes as a side effect
      * of calling list_item().  However, custom functions that maintain lists
      * some other way will need to manage this counter manually.  This is
      * useful for implementing custom message functions and should not be
      * needed otherwise.
      *
      * \param[in,out] out The output functions structure.
      */
     void (*increment_list) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Conclude a list.
      *
      * \note If begin_list was called with non-NULL for both the singular_noun
      *       and plural_noun arguments, this function will output a summary.
      *       Otherwise, no summary will be added.
      *
      * \param[in,out] out The output functions structure.
      */
     void (*end_list) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Should anything be printed to the user?
      *
      * \note This takes into account both the \p quiet value as well as the
      *       current formatter.
      *
      * \param[in] out The output functions structure.
      *
      * \return true if output should be supressed, false otherwise.
      */
     bool (*is_quiet) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Output a spacer.  Not all formatters will do this.
      *
      * \param[in] out The output functions structure.
      */
     void (*spacer) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Output a progress indicator.  This is likely only useful for
      *        plain text, console based formatters.
      *
      * \param[in] out The output functions structure.
      * \param[in] end If true, output a newline afterwards.  This should
      *                only be used the last time this function is called.
      *
      */
     void (*progress) (pcmk__output_t *out, bool end);
 
     /*!
      * \internal
      * \brief Prompt the user for input.  Not all formatters will do this.
      *
      * \note This function is part of pcmk__output_t, but unlike all other
      *       function it does not take that as an argument.  In general, a
      *       prompt will go directly to the screen and therefore bypass any
      *       need to use the formatted output code to decide where and how
      *       to display.
      *
      * \param[in]  prompt The prompt to display.  This is required.
      * \param[in]  echo   If true, echo the user's input to the screen.  Set
      *                    to false for password entry.
      * \param[out] dest   Where to store the user's response.  This is
      *                    required.
      */
     void (*prompt) (const char *prompt, bool echo, char **dest);
 };
 
 /*!
  * \internal
  * \brief Call a formatting function for a previously registered message.
  *
  * \note This function is for implementing custom formatters.  It should not
  *       be called directly.  Instead, call out->message.
  *
  * \param[in,out] out        The output functions structure.
  * \param[in]     message_id The message to be handled.  Unknown messages
  *                           will be ignored.
  * \param[in]     ...        Arguments to be passed to the registered function.
  */
 int
 pcmk__call_message(pcmk__output_t *out, const char *message_id, ...);
 
 /*!
  * \internal
  * \brief Free a ::pcmk__output_t structure that was previously created by
  *        pcmk__output_new().
  *
  * \note While the create and finish functions are designed in such a way that
  *       they can be called repeatedly, this function will completely free the
  *       memory of the object.  Once this function has been called, producing
  *       more output requires starting over from pcmk__output_new().
  *
  * \param[in,out] out         The output structure.
  */
 void pcmk__output_free(pcmk__output_t *out);
 
 /*!
  * \internal
  * \brief Create a new ::pcmk__output_t structure.
  *
  * \param[in,out] out      The destination of the new ::pcmk__output_t.
  * \param[in]     fmt_name How should output be formatted?
  * \param[in]     filename Where should formatted output be written to?  This
  *                         can be a filename (which will be overwritten if it
  *                         already exists), or NULL or "-" for stdout.  For no
  *                         output, pass a filename of "/dev/null".
  * \param[in]     argv     The list of command line arguments.
  *
  * \return Standard Pacemaker return code
  */
 int pcmk__output_new(pcmk__output_t **out, const char *fmt_name,
                      const char *filename, char **argv);
 
 /*!
  * \internal
  * \brief Register a new output formatter, making it available for use
  *        the same as a base formatter.
  *
  * \param[in,out] group   A ::GOptionGroup that formatted output related command
  *                        line arguments should be added to.  This can be NULL
  *                        for use outside of command line programs.
  * \param[in]     name    The name of the format.  This will be used to select a
  *                        format from command line options and for displaying help.
  * \param[in]     create  A function that creates a ::pcmk__output_t.
  * \param[in]     options Format-specific command line options.  These will be
  *                        added to the context.  This argument can also be NULL.
  *
  * \return 0 on success or an error code on error.
  */
 int
 pcmk__register_format(GOptionGroup *group, const char *name,
                       pcmk__output_factory_t create, GOptionEntry *options);
 
 /*!
  * \internal
  * \brief Register an entire table of output formatters at once.
  *
  * \param[in,out] group A ::GOptionGroup that formatted output related command
  *                      line arguments should be added to.  This can be NULL
  *                      for use outside of command line programs.
  * \param[in]     table An array of ::pcmk__supported_format_t which should
  *                      all be registered.  This array must be NULL-terminated.
  *
  */
 void
 pcmk__register_formats(GOptionGroup *group, pcmk__supported_format_t *table);
 
 /*!
  * \internal
  * \brief Unregister a previously registered table of custom formatting
  *        functions and destroy the internal data structures associated with them.
  */
 void
 pcmk__unregister_formats(void);
 
 /*!
  * \internal
  * \brief Register a function to handle a custom message.
  *
  * \note This function is for implementing custom formatters.  It should not
  *       be called directly.  Instead, call out->register_message.
  *
  * \param[in,out] out        The output functions structure.
  * \param[in]     message_id The message to be handled.
  * \param[in]     fn         The custom format function to call for message_id.
  */
 void
 pcmk__register_message(pcmk__output_t *out, const char *message_id,
                        pcmk__message_fn_t fn);
 
 /*!
  * \internal
  * \brief Register an entire table of custom formatting functions at once.
  *
  * This table can contain multiple formatting functions for the same message ID
  * if they are for different format types.
  *
  * \param[in,out] out   The output functions structure.
  * \param[in]     table An array of ::pcmk__message_entry_t values which should
  *                      all be registered.  This array must be NULL-terminated.
  */
 void
 pcmk__register_messages(pcmk__output_t *out, pcmk__message_entry_t *table);
 
 /* Functions that are useful for implementing custom message formatters */
 
 /*!
  * \internal
  * \brief A printf-like function.
  *
  * This function writes to out->dest and indents the text to the current level
  * of the text formatter's nesting.  This should be used when implementing
  * custom message functions instead of printf.
  *
  * \param[in,out] out The output functions structure.
  */
 void
 pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
 
 /*!
  * \internal
  * \brief A vprintf-like function.
  *
  * This function is like pcmk__indented_printf(), except it takes a va_list instead
  * of a list of arguments.  This should be used when implementing custom message
  * functions instead of vprintf.
  *
  * \param[in,out] out    The output functions structure.
  * \param[in]     format The format string.
  * \param[in]     args   A list of arguments to apply to the format string.
  */
 void
 pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args) G_GNUC_PRINTF(2, 0);
 
 
 /*!
  * \internal
  * \brief A printf-like function.
  *
  * This function writes to out->dest without indenting the text.  This should be
  * used with implementing custom message functions instead of printf.
  *
  * \param[in,out] out The output functions structure.
  */
 void
 pcmk__formatted_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
 
 /*!
  * \internal
  * \brief A vprintf-like function.
  *
  * This function is like pcmk__formatted_printf(), except it takes a va_list instead
  * of a list of arguments.  This should be used when implementing custom message
  * functions instead of vprintf.
  *
  * \param[in,out] out    The output functions structure.
  * \param[in]     format The format string.
  * \param[in]     args   A list of arguments to apply to the format string.
  */
 void
 pcmk__formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) G_GNUC_PRINTF(2, 0);
 
 /*!
  * \internal
  * \brief Prompt the user for input.
  *
  * \param[in]  prompt The prompt to display
  * \param[in]  echo   If true, echo the user's input to the screen.  Set
  *                    to false for password entry.
  * \param[out] dest   Where to store the user's response.
  */
 void
 pcmk__text_prompt(const char *prompt, bool echo, char **dest);
 
 /*!
  * \internal
  * \brief Set the log level used by the formatted output logger.
  *
  * \param[in,out] out       The output functions structure.
  * \param[in]     log_level The log level constant (LOG_INFO, LOG_ERR, etc.)
  *                          to use.
  *
  * \note By default, LOG_INFO is used.
  * \note Almost all formatted output messages will respect this setting.
  *       However, out->err will always log at LOG_ERR.
  */
 void
 pcmk__output_set_log_level(pcmk__output_t *out, int log_level);
 
 /*!
  * \internal
  * \brief Create and return a new XML node with the given name, as a child of the
  *        current list parent.  The new node is then added as the new list parent,
  *        meaning all subsequent nodes will be its children.  This is used when
  *        implementing custom functions.
  *
  * \param[in,out] out  The output functions structure.
  * \param[in]     name The name of the node to be created.
  * \param[in]     ...     Name/value pairs to set as XML properties.
  */
 xmlNodePtr
 pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...)
 G_GNUC_NULL_TERMINATED;
 
 /*!
  * \internal
  * \brief Add the given node as a child of the current list parent.  This is
  *        used when implementing custom message functions.
  *
  * \param[in,out] out  The output functions structure.
  * \param[in]     node An XML node to be added as a child.
  */
 void
 pcmk__output_xml_add_node(pcmk__output_t *out, xmlNodePtr node);
 
 /*!
  * \internal
  * \brief Create and return a new XML node with the given name, as a child of the
  *        current list parent.  This is used when implementing custom functions.
  *
  * \param[in,out] out  The output functions structure.
  * \param[in]     name The name of the node to be created.
  * \param[in]     ...     Name/value pairs to set as XML properties.
  */
 xmlNodePtr
 pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...)
 G_GNUC_NULL_TERMINATED;
 
 /*!
  * \internal
  * \brief Like pcmk__output_create_xml_node(), but add the given text content to the
  *        new node.
  *
  * \param[in,out] out     The output functions structure.
  * \param[in]     name    The name of the node to be created.
  * \param[in]     content The text content of the node.
  */
 xmlNodePtr
 pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content);
 
 /*!
  * \internal
  * \brief Push a parent XML node onto the stack.  This is used when implementing
  *        custom message functions.
  *
  * The XML output formatter maintains an internal stack to keep track of which nodes
  * are parents in order to build up the tree structure.  This function can be used
  * to temporarily push a new node onto the stack.  After calling this function, any
  * other formatting functions will have their nodes added as children of this new
  * parent.
  *
  * \param[in,out] out  The output functions structure.
  * \param[in]     node The node to be added/
  */
 void
 pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr node);
 
 /*!
  * \internal
  * \brief Pop a parent XML node onto the stack.  This is used when implementing
  *        custom message functions.
  *
  * This function removes a parent node from the stack.  See pcmk__xml_push_parent()
  * for more details.
  *
  * \note Little checking is done with this function.  Be sure you only pop parents
  * that were previously pushed.  In general, it is best to keep the code between
  * push and pop simple.
  *
  * \param[in,out] out The output functions structure.
  */
 void
 pcmk__output_xml_pop_parent(pcmk__output_t *out);
 
 /*!
  * \internal
  * \brief Peek a parent XML node onto the stack.  This is used when implementing
  *        custom message functions.
  *
  * This function peeks a parent node on stack.  See pcmk__xml_push_parent()
  * for more details. It has no side-effect and can be called for an empty stack.
  *
  * \note Little checking is done with this function.
  *
  * \param[in,out] out The output functions structure.
  *
  * \return NULL if stack is empty, otherwise the parent of the stack.
  */
 xmlNodePtr
 pcmk__output_xml_peek_parent(pcmk__output_t *out);
 
 /*!
  * \internal
  * \brief Create a new XML node consisting of the provided text inside an HTML
  *        element node of the given name.
  *
  * \param[in,out] out          The output functions structure.
  * \param[in]     element_name The name of the new HTML element.
  * \param[in]     id           The CSS ID selector to apply to this element.
  *                             If NULL, no ID is added.
  * \param[in]     class_name   The CSS class selector to apply to this element.
  *                             If NULL, no class is added.
  * \param[in]     text         The text content of the node.
  */
 xmlNodePtr
 pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id,
                               const char *class_name, const char *text);
 
 /*!
  * \internal
  * \brief Add an HTML tag to the <head> section.
  *
  * The arguments after name are a NULL-terminated list of keys and values,
  * all of which will be added as attributes to the given tag.  For instance,
  * the following code would generate the tag "<meta http-equiv='refresh' content='19'>":
  *
  * \code
  * pcmk__html_add_header("meta", "http-equiv", "refresh", "content", "19", NULL);
  * \endcode
  *
  * \param[in]     name   The HTML tag for the new node.
  * \param[in]     ...    A NULL-terminated key/value list of attributes.
  */
 void
 pcmk__html_add_header(const char *name, ...)
 G_GNUC_NULL_TERMINATED;
 
 /*!
  * \internal
  * \brief Handle end-of-program error reporting
  *
  * \param[in,out] error A GError object potentially containing some error.
  *                      If NULL, do nothing.
  * \param[in]     out   The output functions structure.  If NULL, any errors
  *                      will simply be printed to stderr.
  */
 void pcmk__output_and_clear_error(GError *error, pcmk__output_t *out);
 
 #define PCMK__OUTPUT_SPACER_IF(out_obj, cond)   \
     if (cond) {                                 \
         out->spacer(out);                       \
     }
 
 #define PCMK__OUTPUT_LIST_HEADER(out_obj, cond, retcode, title...)  \
     if (retcode == pcmk_rc_no_output) {                             \
         PCMK__OUTPUT_SPACER_IF(out_obj, cond);                      \
         retcode = pcmk_rc_ok;                                       \
         out_obj->begin_list(out_obj, NULL, NULL, title);            \
     }
 
 #define PCMK__OUTPUT_LIST_FOOTER(out_obj, retcode)  \
     if (retcode == pcmk_rc_ok) {                    \
         out_obj->end_list(out_obj);                 \
     }
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif
diff --git a/tools/crm_simulate.c b/tools/crm_simulate.c
index b4aa9d1951..2ea292c724 100644
--- a/tools/crm_simulate.c
+++ b/tools/crm_simulate.c
@@ -1,1187 +1,1190 @@
 /*
  * Copyright 2009-2021 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 <time.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/common/cmdline_internal.h>
 #include <crm/common/output_internal.h>
 #include <crm/common/output.h>
 #include <crm/common/util.h>
 #include <crm/common/iso8601.h>
 #include <crm/pengine/status.h>
 #include <pacemaker-internal.h>
 
 #define SUMMARY "crm_simulate - simulate a Pacemaker cluster's response to events"
 
 struct {
     gboolean all_actions;
     char *dot_file;
     char *graph_file;
     gchar *input_file;
     guint modified;
     GList *node_up;
     GList *node_down;
     GList *node_fail;
     GList *op_fail;
     GList *op_inject;
     gchar *output_file;
     gboolean print_pending;
     gboolean process;
     char *quorum;
     long long repeat;
     gboolean show_attrs;
     gboolean show_failcounts;
     gboolean show_scores;
     gboolean show_utilization;
     gboolean simulate;
     gboolean store;
     gchar *test_dir;
     GList *ticket_grant;
     GList *ticket_revoke;
     GList *ticket_standby;
     GList *ticket_activate;
     char *use_date;
     char *watchdog;
     char *xml_file;
 } options = {
     .print_pending = TRUE,
     .repeat = 1
 };
 
 cib_t *global_cib = NULL;
 bool action_numbers = FALSE;
 char *temp_shadow = NULL;
 extern gboolean bringing_nodes_online;
 crm_exit_t exit_code = CRM_EX_OK;
 
 #define INDENT "                                   "
 
 static pcmk__supported_format_t formats[] = {
     PCMK__SUPPORTED_FORMAT_NONE,
     PCMK__SUPPORTED_FORMAT_TEXT,
     PCMK__SUPPORTED_FORMAT_XML,
     { NULL, NULL, NULL }
 };
 
 static gboolean
 in_place_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.store = TRUE;
     options.process = TRUE;
     options.simulate = TRUE;
     return TRUE;
 }
 
 static gboolean
 live_check_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (options.xml_file) {
         free(options.xml_file);
     }
 
     options.xml_file = NULL;
     return TRUE;
 }
 
 static gboolean
 node_down_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.modified++;
     options.node_down = g_list_append(options.node_down, (gchar *) g_strdup(optarg));
     return TRUE;
 }
 
 static gboolean
 node_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.modified++;
     options.node_fail = g_list_append(options.node_fail, (gchar *) g_strdup(optarg));
     return TRUE;
 }
 
 static gboolean
 node_up_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.modified++;
     bringing_nodes_online = TRUE;
     options.node_up = g_list_append(options.node_up, (gchar *) g_strdup(optarg));
     return TRUE;
 }
 
 static gboolean
 op_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.process = TRUE;
     options.simulate = TRUE;
     options.op_fail = g_list_append(options.op_fail, (gchar *) g_strdup(optarg));
     return TRUE;
 }
 
 static gboolean
 op_inject_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.modified++;
     options.op_inject = g_list_append(options.op_inject, (gchar *) g_strdup(optarg));
     return TRUE;
 }
 
 static gboolean
 quorum_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (options.quorum) {
         free(options.quorum);
     }
 
     options.modified++;
     options.quorum = strdup(optarg);
     return TRUE;
 }
 
 static gboolean
 save_dotfile_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (options.dot_file) {
         free(options.dot_file);
     }
 
     options.process = TRUE;
     options.dot_file = strdup(optarg);
     return TRUE;
 }
 
 static gboolean
 save_graph_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (options.graph_file) {
         free(options.graph_file);
     }
 
     options.process = TRUE;
     options.graph_file = strdup(optarg);
     return TRUE;
 }
 
 static gboolean
 show_scores_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.process = TRUE;
     options.show_scores = TRUE;
     return TRUE;
 }
 
 static gboolean
 simulate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.process = TRUE;
     options.simulate = TRUE;
     return TRUE;
 }
 
 static gboolean
 ticket_activate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.modified++;
     options.ticket_activate = g_list_append(options.ticket_activate, (gchar *) g_strdup(optarg));
     return TRUE;
 }
 
 static gboolean
 ticket_grant_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.modified++;
     options.ticket_grant = g_list_append(options.ticket_grant, (gchar *) g_strdup(optarg));
     return TRUE;
 }
 
 static gboolean
 ticket_revoke_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.modified++;
     options.ticket_revoke = g_list_append(options.ticket_revoke, (gchar *) g_strdup(optarg));
     return TRUE;
 }
 
 static gboolean
 ticket_standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.modified++;
     options.ticket_standby = g_list_append(options.ticket_standby, (gchar *) g_strdup(optarg));
     return TRUE;
 }
 
 static gboolean
 utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.process = TRUE;
     options.show_utilization = TRUE;
     return TRUE;
 }
 
 static gboolean
 watchdog_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (options.watchdog) {
         free(options.watchdog);
     }
 
     options.modified++;
     options.watchdog = strdup(optarg);
     return TRUE;
 }
 
 static gboolean
 xml_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (options.xml_file) {
         free(options.xml_file);
     }
 
     options.xml_file = strdup(optarg);
     return TRUE;
 }
 
 static gboolean
 xml_pipe_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (options.xml_file) {
         free(options.xml_file);
     }
 
     options.xml_file = strdup("-");
     return TRUE;
 }
 
 static GOptionEntry operation_entries[] = {
     { "run", 'R', 0, G_OPTION_ARG_NONE, &options.process,
       "Process the supplied input and show what actions the cluster will take in response",
       NULL },
     { "simulate", 'S', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, simulate_cb,
       "Like --run, but also simulate taking those actions and show the resulting new status",
       NULL },
     { "in-place", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, in_place_cb,
       "Like --simulate, but also store the results back to the input file",
       NULL },
     { "show-attrs", 'A', 0, G_OPTION_ARG_NONE, &options.show_attrs,
       "Show node attributes",
       NULL },
     { "show-failcounts", 'c', 0, G_OPTION_ARG_NONE, &options.show_failcounts,
       "Show resource fail counts",
       NULL },
     { "show-scores", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_scores_cb,
       "Show allocation scores",
       NULL },
     { "show-utilization", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb,
       "Show utilization information",
       NULL },
     { "profile", 'P', 0, G_OPTION_ARG_FILENAME, &options.test_dir,
       "Process all the XML files in the named directory to create profiling data",
       "DIR" },
     { "repeat", 'N', 0, G_OPTION_ARG_INT, &options.repeat,
       "With --profile, repeat each test N times and print timings",
       "N" },
     /* Deprecated */
     { "pending", 'j', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.print_pending,
       "Display pending state if 'record-pending' is enabled",
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry synthetic_entries[] = {
     { "node-up", 'u', 0, G_OPTION_ARG_CALLBACK, node_up_cb,
       "Simulate bringing a node online",
       "NODE" },
     { "node-down", 'd', 0, G_OPTION_ARG_CALLBACK, node_down_cb,
       "Simulate taking a node offline",
       "NODE" },
     { "node-fail", 'f', 0, G_OPTION_ARG_CALLBACK, node_fail_cb,
       "Simulate a node failing",
       "NODE" },
     { "op-inject", 'i', 0, G_OPTION_ARG_CALLBACK, op_inject_cb,
       "Generate a failure for the cluster to react to in the simulation.\n"
       INDENT "See `Operation Specification` help for more information.",
       "OPSPEC" },
     { "op-fail", 'F', 0, G_OPTION_ARG_CALLBACK, op_fail_cb,
       "If the specified task occurs during the simulation, have it fail with return code ${rc}.\n"
       INDENT "The transition will normally stop at the failed action.\n"
       INDENT "Save the result with --save-output and re-run with --xml-file.\n"
       INDENT "See `Operation Specification` help for more information.",
       "OPSPEC" },
     { "set-datetime", 't', 0, G_OPTION_ARG_STRING, &options.use_date,
       "Set date/time (ISO 8601 format, see https://en.wikipedia.org/wiki/ISO_8601)",
       "DATETIME" },
     { "quorum", 'q', 0, G_OPTION_ARG_CALLBACK, quorum_cb,
       "Set to '1' (or 'true') to indicate cluster has quorum",
       "QUORUM" },
     { "watchdog", 'w', 0, G_OPTION_ARG_CALLBACK, watchdog_cb,
       "Set to '1' (or 'true') to indicate cluster has an active watchdog device",
       "DEVICE" },
     { "ticket-grant", 'g', 0, G_OPTION_ARG_CALLBACK, ticket_grant_cb,
       "Simulate granting a ticket",
       "TICKET" },
     { "ticket-revoke", 'r', 0, G_OPTION_ARG_CALLBACK, ticket_revoke_cb,
       "Simulate revoking a ticket",
       "TICKET" },
     { "ticket-standby", 'b', 0, G_OPTION_ARG_CALLBACK, ticket_standby_cb,
       "Simulate making a ticket standby",
       "TICKET" },
     { "ticket-activate", 'e', 0, G_OPTION_ARG_CALLBACK, ticket_activate_cb,
       "Simulate activating a ticket",
       "TICKET" },
 
     { NULL }
 };
 
 static GOptionEntry artifact_entries[] = {
     { "save-input", 'I', 0, G_OPTION_ARG_FILENAME, &options.input_file,
       "Save the input configuration to the named file",
       "FILE" },
     { "save-output", 'O', 0, G_OPTION_ARG_FILENAME, &options.output_file,
       "Save the output configuration to the named file",
       "FILE" },
     { "save-graph", 'G', 0, G_OPTION_ARG_CALLBACK, save_graph_cb,
       "Save the transition graph (XML format) to the named file",
       "FILE" },
     { "save-dotfile", 'D', 0, G_OPTION_ARG_CALLBACK, save_dotfile_cb,
       "Save the transition graph (DOT format) to the named file",
       "FILE" },
     { "all-actions", 'a', 0, G_OPTION_ARG_NONE, &options.all_actions,
       "Display all possible actions in DOT graph (even if not part of transition)",
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry source_entries[] = {
     { "live-check", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, live_check_cb,
       "Connect to CIB manager and use the current CIB contents as input",
       NULL },
     { "xml-file", 'x', 0, G_OPTION_ARG_CALLBACK, xml_file_cb,
       "Retrieve XML from the named file",
       "FILE" },
     { "xml-pipe", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, xml_pipe_cb,
       "Retrieve XML from stdin",
       NULL },
 
     { NULL }
 };
 
 static void
 get_date(pe_working_set_t *data_set, bool print_original, char *use_date)
 {
     pcmk__output_t *out = data_set->priv;
     time_t original_date = 0;
 
     crm_element_value_epoch(data_set->input, "execution-date", &original_date);
 
     if (use_date) {
         data_set->now = crm_time_new(use_date);
         out->info(out, "Setting effective cluster time: %s", use_date);
         crm_time_log(LOG_NOTICE, "Pretending 'now' is", data_set->now,
                      crm_time_log_date | crm_time_log_timeofday);
 
 
     } else if (original_date) {
 
         data_set->now = crm_time_new(NULL);
         crm_time_set_timet(data_set->now, &original_date);
 
         if (print_original) {
             char *when = crm_time_as_string(data_set->now,
                             crm_time_log_date|crm_time_log_timeofday);
 
             out->info(out, "Using the original execution date of: %s", when);
             free(when);
         }
     }
 }
 
 static void
 print_cluster_status(pe_working_set_t * data_set, unsigned int show_opts)
 {
     pcmk__output_t *out = data_set->priv;
     int rc = pcmk_rc_no_output;
     GList *all = NULL;
 
     all = g_list_prepend(all, strdup("*"));
 
     rc = out->message(out, "node-list", data_set->nodes, all, all, show_opts);
     PCMK__OUTPUT_SPACER_IF(out, rc == pcmk_rc_ok);
     rc = out->message(out, "resource-list", data_set, show_opts | pcmk_show_inactive_rscs,
                       FALSE, all, all, FALSE);
 
     if (options.show_attrs) {
-        out->message(out, "node-attribute-list", data_set,
-                     0, rc == pcmk_rc_ok, all, all);
+        rc = out->message(out, "node-attribute-list", data_set,
+                          0, rc == pcmk_rc_ok, all, all);
     }
 
     if (options.show_failcounts) {
+        rc = out->message(out, "node-summary", data_set, all, all,
+                          0, show_opts, rc == pcmk_rc_ok);
+
         out->message(out, "failed-action-list", data_set, all, all,
                      rc == pcmk_rc_ok);
     }
 
     g_list_free_full(all, free);
 }
 
 static char *
 create_action_name(pe_action_t *action)
 {
     char *action_name = NULL;
     const char *prefix = "";
     const char *action_host = NULL;
     const char *clone_name = NULL;
     const char *task = action->task;
 
     if (action->node) {
         action_host = action->node->details->uname;
     } else if (!pcmk_is_set(action->flags, pe_action_pseudo)) {
         action_host = "<none>";
     }
 
     if (pcmk__str_eq(action->task, RSC_CANCEL, pcmk__str_casei)) {
         prefix = "Cancel ";
         task = action->cancel_task;
     }
 
     if (action->rsc && action->rsc->clone_name) {
         clone_name = action->rsc->clone_name;
     }
 
     if (clone_name) {
         char *key = NULL;
         guint interval_ms = 0;
 
         if (pcmk__guint_from_hash(action->meta,
                                   XML_LRM_ATTR_INTERVAL_MS, 0,
                                   &interval_ms) != pcmk_rc_ok) {
             interval_ms = 0;
         }
 
         if (pcmk__strcase_any_of(action->task, RSC_NOTIFY, RSC_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");
 
             CRM_ASSERT(n_type != NULL);
             CRM_ASSERT(n_task != NULL);
             key = pcmk__notify_key(clone_name, n_type, n_task);
 
         } else {
             key = pcmk__op_key(clone_name, task, interval_ms);
         }
 
         if (action_host) {
             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, CRM_OP_FENCE, pcmk__str_casei)) {
         const char *op = g_hash_table_lookup(action->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 (action_numbers) { // i.e. verbose
         char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id);
 
         free(action_name);
         action_name = with_id;
     }
     return action_name;
 }
 
 static bool
 create_dotfile(pe_working_set_t * data_set, const char *dot_file, gboolean all_actions,
                GError **error)
 {
     GList *gIter = NULL;
     FILE *dot_strm = fopen(dot_file, "w");
 
     if (dot_strm == NULL) {
         g_set_error(error, PCMK__RC_ERROR, errno,
                     "Could not open %s for writing: %s", dot_file,
                     pcmk_rc_str(errno));
         return false;
     }
 
     fprintf(dot_strm, " digraph \"g\" {\n");
     for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
         pe_action_t *action = (pe_action_t *) gIter->data;
         const char *style = "dashed";
         const char *font = "black";
         const char *color = "black";
         char *action_name = create_action_name(action);
 
         crm_trace("Action %d: %s %s %p", action->id, action_name, action->uuid, action);
 
         if (pcmk_is_set(action->flags, pe_action_pseudo)) {
             font = "orange";
         }
 
         if (pcmk_is_set(action->flags, pe_action_dumped)) {
             style = "bold";
             color = "green";
 
         } else if ((action->rsc != NULL)
                    && !pcmk_is_set(action->rsc->flags, pe_rsc_managed)) {
             color = "red";
             font = "purple";
             if (all_actions == FALSE) {
                 goto do_not_write;
             }
 
         } else if (pcmk_is_set(action->flags, pe_action_optional)) {
             color = "blue";
             if (all_actions == FALSE) {
                 goto do_not_write;
             }
 
         } else {
             color = "red";
             CRM_CHECK(!pcmk_is_set(action->flags, pe_action_runnable), ;);
         }
 
         pe__set_action_flags(action, pe_action_dumped);
         crm_trace("\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]",
                 action_name, style, color, font);
         fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n",
                 action_name, style, color, font);
   do_not_write:
         free(action_name);
     }
 
     for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
         pe_action_t *action = (pe_action_t *) gIter->data;
 
         GList *gIter2 = NULL;
 
         for (gIter2 = action->actions_before; gIter2 != NULL; gIter2 = gIter2->next) {
             pe_action_wrapper_t *before = (pe_action_wrapper_t *) gIter2->data;
 
             char *before_name = NULL;
             char *after_name = NULL;
             const char *style = "dashed";
             gboolean optional = TRUE;
 
             if (before->state == pe_link_dumped) {
                 optional = FALSE;
                 style = "bold";
             } else if (pcmk_is_set(action->flags, pe_action_pseudo)
                        && (before->type & pe_order_stonith_stop)) {
                 continue;
             } else if (before->type == pe_order_none) {
                 continue;
             } else if (pcmk_is_set(before->action->flags, pe_action_dumped)
                        && pcmk_is_set(action->flags, pe_action_dumped)
                        && before->type != pe_order_load) {
                 optional = FALSE;
             }
 
             if (all_actions || optional == FALSE) {
                 before_name = create_action_name(before->action);
                 after_name = create_action_name(action);
                 crm_trace("\"%s\" -> \"%s\" [ style = %s]",
                         before_name, after_name, style);
                 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 true;
 }
 
 static int
 setup_input(const char *input, const char *output, GError **error)
 {
     int rc = pcmk_rc_ok;
     cib_t *cib_conn = NULL;
     xmlNode *cib_object = NULL;
     char *local_output = NULL;
 
     if (input == NULL) {
         /* Use live CIB */
         cib_conn = cib_new();
         rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
         rc = pcmk_legacy2rc(rc);
 
         if (rc == pcmk_rc_ok) {
             rc = cib_conn->cmds->query(cib_conn, NULL, &cib_object, cib_scope_local | cib_sync_call);
         }
 
         cib_conn->cmds->signoff(cib_conn);
         cib_delete(cib_conn);
         cib_conn = NULL;
 
         if (rc != pcmk_rc_ok) {
             rc = pcmk_legacy2rc(rc);
             g_set_error(error, PCMK__RC_ERROR, rc,
                         "Live CIB query failed: %s (%d)", pcmk_rc_str(rc), rc);
             return rc;
 
         } else if (cib_object == NULL) {
             g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_NOINPUT,
                         "Live CIB query failed: empty result");
             return pcmk_rc_no_input;
         }
 
     } else if (pcmk__str_eq(input, "-", pcmk__str_casei)) {
         cib_object = filename2xml(NULL);
 
     } else {
         cib_object = filename2xml(input);
     }
 
     if (get_object_root(XML_CIB_TAG_STATUS, cib_object) == NULL) {
         create_xml_node(cib_object, XML_CIB_TAG_STATUS);
     }
 
     if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
         free_xml(cib_object);
         return pcmk_rc_transform_failed;
     }
 
     if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
         free_xml(cib_object);
         return pcmk_rc_schema_validation;
     }
 
     if (output == NULL) {
         char *pid = pcmk__getpid_s();
 
         local_output = get_shadow_file(pid);
         temp_shadow = strdup(local_output);
         output = local_output;
         free(pid);
     }
 
     rc = write_xml_file(cib_object, output, FALSE);
     free_xml(cib_object);
     cib_object = NULL;
 
     if (rc < 0) {
         rc = pcmk_legacy2rc(rc);
         g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_CANTCREAT,
                     "Could not create '%s': %s", output, pcmk_rc_str(rc));
         return rc;
     } else {
         setenv("CIB_file", output, 1);
         free(local_output);
         return pcmk_rc_ok;
     }
 }
 
 static void
 profile_one(const char *xml_file, long long repeat, pe_working_set_t *data_set, char *use_date)
 {
     pcmk__output_t *out = data_set->priv;
     xmlNode *cib_object = NULL;
     clock_t start = 0;
     clock_t end;
 
     cib_object = filename2xml(xml_file);
     start = clock();
 
     if (get_object_root(XML_CIB_TAG_STATUS, cib_object) == NULL) {
         create_xml_node(cib_object, XML_CIB_TAG_STATUS);
     }
 
 
     if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
         free_xml(cib_object);
         return;
     }
 
     if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
         free_xml(cib_object);
         return;
     }
 
     for (int i = 0; i < repeat; ++i) {
         xmlNode *input = (repeat == 1)? cib_object : copy_xml(cib_object);
 
         data_set->input = input;
         get_date(data_set, false, use_date);
         pcmk__schedule_actions(data_set, input, NULL);
         pe_reset_working_set(data_set);
     }
 
     end = clock();
     out->message(out, "profile", xml_file, start, end);
 }
 
 #ifndef FILENAME_MAX
 #  define FILENAME_MAX 512
 #endif
 
 static void
 profile_all(const char *dir, long long repeat, pe_working_set_t *data_set, char *use_date)
 {
     pcmk__output_t *out = data_set->priv;
     struct dirent **namelist;
 
     int file_num = scandir(dir, &namelist, 0, alphasort);
 
     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_one(buffer, repeat, data_set, use_date);
             }
             free(namelist[file_num]);
         }
         free(namelist);
 
         out->end_list(out);
     }
 }
 
 PCMK__OUTPUT_ARGS("profile", "const char *", "clock_t", "clock_t")
 static int
 profile_default(pcmk__output_t *out, va_list args) {
     const char *xml_file = va_arg(args, const char *);
     clock_t start = va_arg(args, clock_t);
     clock_t end = va_arg(args, clock_t);
 
     out->list_item(out, NULL, "Testing %s ... %.2f secs", xml_file,
                    (end - start) / (float) CLOCKS_PER_SEC);
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("profile", "const char *", "clock_t", "clock_t")
 static int
 profile_xml(pcmk__output_t *out, va_list args) {
     const char *xml_file = va_arg(args, const char *);
     clock_t start = va_arg(args, clock_t);
     clock_t end = va_arg(args, clock_t);
 
     char *duration = pcmk__ftoa((end - start) / (float) CLOCKS_PER_SEC);
 
     pcmk__output_create_xml_node(out, "timing",
                                  "file", xml_file,
                                  "duration", duration,
                                  NULL);
 
     free(duration);
     return pcmk_rc_ok;
 }
 
 static pcmk__message_entry_t fmt_functions[] = {
     { "profile", "default", profile_default, },
     { "profile", "xml", profile_xml },
 
     { NULL }
 };
 
 static void
 crm_simulate_register_messages(pcmk__output_t *out) {
     pcmk__register_messages(out, fmt_functions);
 }
 
 static GOptionContext *
 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
     GOptionContext *context = NULL;
 
     GOptionEntry extra_prog_entries[] = {
         { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
           "Display only essential output",
           NULL },
 
         { NULL }
     };
 
     const char *description = "Operation Specification:\n\n"
                               "The OPSPEC in any command line option is of the form\n"
                               "${resource}_${task}_${interval_in_ms}@${node}=${rc}\n"
                               "(memcached_monitor_20000@bart.example.com=7, for example).\n"
                               "${rc} is an OCF return code.  For more information on these\n"
                               "return codes, refer to https://clusterlabs.org/pacemaker/doc/2.1/Pacemaker_Administration/html/agents.html#ocf-return-codes\n\n"
                               "Examples:\n\n"
                               "Pretend a recurring monitor action found memcached stopped on node\n"
                               "fred.example.com and, during recovery, that the memcached stop\n"
                               "action failed:\n\n"
                               "\tcrm_simulate -LS --op-inject memcached:0_monitor_20000@bart.example.com=7 "
                               "--op-fail memcached:0_stop_0@fred.example.com=1 --save-output /tmp/memcached-test.xml\n\n"
                               "Now see what the reaction to the stop failed would be:\n\n"
                               "\tcrm_simulate -S --xml-file /tmp/memcached-test.xml\n\n";
 
     context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
     pcmk__add_main_args(context, extra_prog_entries);
     g_option_context_set_description(context, description);
 
     pcmk__add_arg_group(context, "operations", "Operations:",
                         "Show operations options", operation_entries);
     pcmk__add_arg_group(context, "synthetic", "Synthetic Cluster Events:",
                         "Show synthetic cluster event options", synthetic_entries);
     pcmk__add_arg_group(context, "artifact", "Artifact Options:",
                         "Show artifact options", artifact_entries);
     pcmk__add_arg_group(context, "source", "Data Source:",
                         "Show data source options", source_entries);
 
     return context;
 }
 
 int
 main(int argc, char **argv)
 {
     int printed = pcmk_rc_no_output;
     int rc = pcmk_rc_ok;
     pe_working_set_t *data_set = NULL;
     pcmk__output_t *out = NULL;
     xmlNode *input = NULL;
 
     GError *error = NULL;
 
     GOptionGroup *output_group = NULL;
     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
     gchar **processed_args = pcmk__cmdline_preproc(argv, "bdefgiqrtuwxDFGINO");
     GOptionContext *context = build_arg_context(args, &output_group);
 
     /* This must come before g_option_context_parse_strv. */
     options.xml_file = strdup("-");
 
     pcmk__register_formats(output_group, formats);
     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     pcmk__cli_init_logging("crm_simulate", args->verbosity);
 
     rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
     if (rc != pcmk_rc_ok) {
         fprintf(stderr, "Error creating output format %s: %s\n",
                 args->output_ty, pcmk_rc_str(rc));
         exit_code = CRM_EX_ERROR;
         goto done;
     }
 
     if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches) &&
         !options.show_scores && !options.show_utilization) {
         pcmk__force_args(context, &error, "%s --text-fancy", g_get_prgname());
     } else if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) {
         pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname());
     }
 
     crm_simulate_register_messages(out);
     pe__register_messages(out);
     pcmk__register_lib_messages(out);
 
     out->quiet = args->quiet;
 
     if (args->version) {
         out->version(out, false);
         goto done;
     }
 
     if (args->verbosity > 0) {
 #ifdef PCMK__COMPAT_2_0
         /* Redirect stderr to stdout so we can grep the output */
         close(STDERR_FILENO);
         dup2(STDOUT_FILENO, STDERR_FILENO);
 #endif
         action_numbers = TRUE;
     }
 
     data_set = pe_new_working_set();
     if (data_set == NULL) {
         rc = ENOMEM;
         g_set_error(&error, PCMK__RC_ERROR, rc, "Could not allocate working set");
         goto done;
     }
 
     if (options.show_scores) {
         pe__set_working_set_flags(data_set, pe_flag_show_scores);
     }
     if (options.show_utilization) {
         pe__set_working_set_flags(data_set, pe_flag_show_utilization);
     }
     pe__set_working_set_flags(data_set, pe_flag_no_compat);
 
     if (options.test_dir != NULL) {
         data_set->priv = out;
         profile_all(options.test_dir, options.repeat, data_set, options.use_date);
         rc = pcmk_rc_ok;
         goto done;
     }
 
     rc = setup_input(options.xml_file, options.store ? options.xml_file : options.output_file, &error);
     if (rc != pcmk_rc_ok) {
         goto done;
     }
 
     global_cib = cib_new();
     rc = global_cib->cmds->signon(global_cib, crm_system_name, cib_command);
     if (rc != pcmk_rc_ok) {
         rc = pcmk_legacy2rc(rc);
         g_set_error(&error, PCMK__RC_ERROR, rc,
                     "Could not connect to the CIB: %s", pcmk_rc_str(rc));
         goto done;
     }
 
     rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call | cib_scope_local);
     if (rc != pcmk_rc_ok) {
         rc = pcmk_legacy2rc(rc);
         g_set_error(&error, PCMK__RC_ERROR, rc,
                     "Could not get local CIB: %s", pcmk_rc_str(rc));
         goto done;
     }
 
     data_set->input = input;
     data_set->priv = out;
     get_date(data_set, true, options.use_date);
     if(options.xml_file) {
         pe__set_working_set_flags(data_set, pe_flag_sanitized);
     }
     if (options.show_scores) {
         pe__set_working_set_flags(data_set, pe_flag_show_scores);
     }
     if (options.show_utilization) {
         pe__set_working_set_flags(data_set, pe_flag_show_utilization);
     }
     cluster_status(data_set);
 
     if (!out->is_quiet(out)) {
         unsigned int show_opts = options.print_pending ? pcmk_show_pending : 0;
 
         if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) {
             printed = out->message(out, "maint-mode", data_set->flags);
         }
 
         if (data_set->disabled_resources || data_set->blocked_resources) {
             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",
                                 data_set->disabled_resources, data_set->ninstances,
                                 data_set->blocked_resources);
         }
 
         PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
         /* Most formatted output headers use caps for each word, but this one
          * only has the first word capitalized for compatibility with pcs.
          */
         out->begin_list(out, NULL, NULL, "Current cluster status");
         print_cluster_status(data_set, show_opts);
         out->end_list(out);
         printed = pcmk_rc_ok;
     }
 
     if (options.modified) {
         PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
         modify_configuration(data_set, global_cib, options.quorum, options.watchdog, options.node_up,
                              options.node_down, options.node_fail, options.op_inject,
                              options.ticket_grant, options.ticket_revoke, options.ticket_standby,
                              options.ticket_activate);
         printed = pcmk_rc_ok;
 
         rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call);
         if (rc != pcmk_rc_ok) {
             rc = pcmk_legacy2rc(rc);
             g_set_error(&error, PCMK__RC_ERROR, rc,
                         "Could not get modified CIB: %s", pcmk_rc_str(rc));
             goto done;
         }
 
         cleanup_calculations(data_set);
         data_set->input = input;
         data_set->priv = out;
         get_date(data_set, true, options.use_date);
 
         if(options.xml_file) {
             pe__set_working_set_flags(data_set, pe_flag_sanitized);
         }
         if (options.show_scores) {
             pe__set_working_set_flags(data_set, pe_flag_show_scores);
         }
         if (options.show_utilization) {
             pe__set_working_set_flags(data_set, pe_flag_show_utilization);
         }
         cluster_status(data_set);
     }
 
     if (options.input_file != NULL) {
         rc = write_xml_file(input, options.input_file, FALSE);
         if (rc < 0) {
             rc = pcmk_legacy2rc(rc);
             g_set_error(&error, PCMK__RC_ERROR, rc,
                         "Could not create '%s': %s", options.input_file, pcmk_rc_str(rc));
             goto done;
         }
     }
 
     if (options.process || options.simulate) {
         crm_time_t *local_date = NULL;
         pcmk__output_t *logger_out = NULL;
 
         if (pcmk_all_flags_set(data_set->flags, pe_flag_show_scores|pe_flag_show_utilization)) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL, "Allocation Scores and Utilization Information");
             printed = pcmk_rc_ok;
         } else if (pcmk_is_set(data_set->flags, pe_flag_show_scores)) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL, "Allocation Scores");
             printed = pcmk_rc_ok;
         } else if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL, "Utilization Information");
             printed = pcmk_rc_ok;
         } else {
             logger_out = pcmk__new_logger();
             if (logger_out == NULL) {
                 goto done;
             }
 
             data_set->priv = logger_out;
         }
 
         pcmk__schedule_actions(data_set, input, local_date);
 
         if (logger_out == NULL) {
             out->end_list(out);
         } else {
             logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
             pcmk__output_free(logger_out);
             data_set->priv = out;
         }
 
         input = NULL;           /* Don't try and free it twice */
 
         if (options.graph_file != NULL) {
             write_xml_file(data_set->graph, options.graph_file, FALSE);
         }
 
         if (options.dot_file != NULL) {
             if (!create_dotfile(data_set, options.dot_file, options.all_actions, &error)) {
                 goto done;
             }
         }
 
         if (!out->is_quiet(out)) {
             GList *gIter = NULL;
 
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL, "Transition Summary");
 
             LogNodeActions(data_set);
             for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
                 pe_resource_t *rsc = (pe_resource_t *) gIter->data;
 
                 LogActions(rsc, data_set);
             }
 
             out->end_list(out);
             printed = pcmk_rc_ok;
         }
     }
 
     rc = pcmk_rc_ok;
 
     if (options.simulate) {
         PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
         if (run_simulation(data_set, global_cib, options.op_fail) != pcmk_rc_ok) {
             rc = pcmk_rc_error;
         }
 
         printed = pcmk_rc_ok;
 
         if (!out->is_quiet(out)) {
             get_date(data_set, true, options.use_date);
 
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL, "Revised Cluster Status");
 
             if (options.show_scores) {
                 pe__set_working_set_flags(data_set, pe_flag_show_scores);
             }
             if (options.show_utilization) {
                 pe__set_working_set_flags(data_set, pe_flag_show_utilization);
             }
 
             cluster_status(data_set);
             print_cluster_status(data_set, 0);
 
             out->end_list(out);
         }
     }
 
   done:
     pcmk__output_and_clear_error(error, NULL);
 
     /* There sure is a lot to free in options. */
     free(options.dot_file);
     free(options.graph_file);
     g_free(options.input_file);
     g_list_free_full(options.node_up, g_free);
     g_list_free_full(options.node_down, g_free);
     g_list_free_full(options.node_fail, g_free);
     g_list_free_full(options.op_fail, g_free);
     g_list_free_full(options.op_inject, g_free);
     g_free(options.output_file);
     free(options.quorum);
     g_free(options.test_dir);
     g_list_free_full(options.ticket_grant, g_free);
     g_list_free_full(options.ticket_revoke, g_free);
     g_list_free_full(options.ticket_standby, g_free);
     g_list_free_full(options.ticket_activate, g_free);
     free(options.use_date);
     free(options.watchdog);
     free(options.xml_file);
 
     pcmk__free_arg_context(context);
     g_strfreev(processed_args);
 
     if (data_set) {
         pe_free_working_set(data_set);
     }
 
     if (global_cib) {
         global_cib->cmds->signoff(global_cib);
         cib_delete(global_cib);
     }
 
     fflush(stderr);
 
     if (temp_shadow) {
         unlink(temp_shadow);
         free(temp_shadow);
     }
 
     if (rc != pcmk_rc_ok) {
         exit_code = pcmk_rc2exitc(rc);
     }
 
     if (out != NULL) {
         out->finish(out, exit_code, true, NULL);
         pcmk__output_free(out);
     }
 
     crm_exit(exit_code);
 }
diff --git a/xml/Makefile.am b/xml/Makefile.am
index b9448d4ee7..8e7b6d3092 100644
--- a/xml/Makefile.am
+++ b/xml/Makefile.am
@@ -1,298 +1,298 @@
 #
 # Copyright 2004-2021 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 $(top_srcdir)/mk/common.mk
 
 noarch_pkgconfig_DATA	= $(builddir)/pacemaker-schemas.pc
 
 # Pacemaker has 3 schemas: the CIB schema, the API schema (for command-line
 # tool XML output), and a legacy schema for crm_mon --as-xml.
 #
 # See README.md for details on updating CIB schema files (API is similar)
 
 # The CIB and crm_mon schemas are installed directly in CRM_SCHEMA_DIRECTORY
 # for historical reasons, while the API schema is installed in a subdirectory.
 APIdir	= $(CRM_SCHEMA_DIRECTORY)/api
 CIBdir	= $(CRM_SCHEMA_DIRECTORY)
 MONdir	= $(CRM_SCHEMA_DIRECTORY)
 
 # Extract a sorted list of available numeric schema versions
 # from filenames like NAME-MAJOR[.MINOR][.MINOR-MINOR].rng
 numeric_versions = $(shell ls -1 $(1) \
 			  | sed -n -e 's/^.*-\([0-9][0-9.]*\).rng$$/\1/p' \
 			  | sort -u -t. -k 1,1n -k 2,2n -k 3,3n)
 
 version_pairs = $(join \
 			    $(1),$(addprefix \
 			      -,$(wordlist \
 			        2,$(words $(1)),$(1) \
 			      ) next \
 			    ) \
 			  )
 
 version_pairs_last = $(wordlist \
 			    $(words \
 			      $(wordlist \
 			        2,$(1),$(2) \
 			      ) \
 			    ),$(1),$(2) \
 			  )
 
 # NOTE: All files in API_request_base, CIB_cfg_base, API_base, and CIB_base
 # need to start with a unique prefix.  These variables all get iterated over
 # and globbed, and two files starting with the same prefix will cause
 # problems.
 
 # Names of API schemas that form the choices for pacemaker-result content
 API_request_base	= command-output	\
 			  crm_mon		\
 			  crm_resource		\
 			  crm_simulate		\
 			  crmadmin		\
 			  digests		\
 			  pacemakerd 		\
 			  stonith_admin		\
 			  version
 
 # Names of CIB schemas that form the choices for cib/configuration content
 CIB_cfg_base		= options nodes resources constraints fencing acls tags alerts
 
 # Names of all schemas (including top level and those included by others)
-API_base		= $(API_request_base) fence-event failure generic-list item node-attrs nodes resources status
+API_base		= $(API_request_base) fence-event failure generic-list item node-attrs node-history nodes resources status
 CIB_base		= cib $(CIB_cfg_base) status score rule nvset
 
 # Static schema files and transforms (only CIB has transforms)
 # 
 # This is more complicated than it should be due to the need to support
 # VPATH builds and "make distcheck". We need the absolute paths for reliable
 # substitution back and forth, and relative paths for distributed files.
 API_abs_files		= $(foreach base,$(API_base),$(wildcard $(abs_srcdir)/api/$(base)-*.rng))
 CIB_abs_files		= $(foreach base,$(CIB_base),$(wildcard $(abs_srcdir)/$(base).rng $(abs_srcdir)/$(base)-*.rng))
 CIB_abs_xsl		= $(abs_srcdir)/upgrade-1.3.xsl			\
 			  $(abs_srcdir)/upgrade-2.10.xsl		\
 			  $(wildcard $(abs_srcdir)/upgrade-*enter.xsl)	\
 			  $(wildcard $(abs_srcdir)/upgrade-*leave.xsl)
 MON_abs_files 		= $(abs_srcdir)/crm_mon.rng
 API_files		= $(foreach base,$(API_base),$(wildcard $(srcdir)/api/$(base)-*.rng))
 CIB_files		= $(foreach base,$(CIB_base),$(wildcard $(srcdir)/$(base).rng $(srcdir)/$(base)-*.rng))
 CIB_xsl			= $(srcdir)/upgrade-1.3.xsl			\
 			  $(srcdir)/upgrade-2.10.xsl		\
 			  $(wildcard $(srcdir)/upgrade-*enter.xsl)	\
 			  $(wildcard $(srcdir)/upgrade-*leave.xsl)
 MON_files 		= $(srcdir)/crm_mon.rng
 
 # Sorted lists of all numeric schema versions
 API_numeric_versions	= $(call numeric_versions,${API_files})
 CIB_numeric_versions	= $(call numeric_versions,${CIB_files})
 MON_numeric_versions 	= $(call numeric_versions,$(wildcard $(srcdir)/api/crm_mon*.rng))
 
 # The highest numeric schema version
 API_max			?= $(lastword $(API_numeric_versions))
 CIB_max			?= $(lastword $(CIB_numeric_versions))
 MON_max 			?= $(lastword $(MON_numeric_versions))
 
 # Sorted lists of all schema versions (including "next")
 API_versions		= next $(API_numeric_versions)
 CIB_versions		= next $(CIB_numeric_versions)
 
 # Build tree locations of static schema files and transforms (for VPATH builds)
 API_build_copies	= $(foreach f,$(API_abs_files),$(subst $(abs_srcdir),$(abs_builddir),$(f)))
 CIB_build_copies	= $(foreach f,$(CIB_abs_files) $(CIB_abs_xsl),$(subst $(abs_srcdir),$(abs_builddir),$(f)))
 MON_build_copies 	= $(foreach f,$(MON_abs_files),$(subst $(abs_srcdir),$(abs_builddir),$(f)))
 
 # Dynamically generated schema files
 API_generated		= api/api-result.rng $(foreach base,$(API_versions),api/api-result-$(base).rng)
 CIB_generated		= pacemaker.rng $(foreach base,$(CIB_versions),pacemaker-$(base).rng) versions.rng
 MON_generated 		= crm_mon.rng
 
 CIB_version_pairs	= $(call version_pairs,${CIB_numeric_versions})
 CIB_version_pairs_cnt	= $(words ${CIB_version_pairs})
 CIB_version_pairs_last  = $(call version_pairs_last,${CIB_version_pairs_cnt},${CIB_version_pairs})
 
 dist_API_DATA		= $(API_files)
 dist_CIB_DATA		= $(CIB_files) $(CIB_xsl)
 
 nodist_API_DATA		= $(API_generated)
 nodist_CIB_DATA		= $(CIB_generated)
 nodist_MON_DATA		= $(MON_generated)
 
 EXTRA_DIST		= README.md			\
 			  best-match.sh			\
 			  cibtr-2.rng			\
 			  context-of.xsl		\
 			  ocf-meta2man.xsl		\
 			  regression.sh			\
 			  upgrade-2.10-roundtrip.xsl	\
 			  upgrade-detail.xsl		\
 			  xslt_cibtr-2.rng		\
 			  assets			\
 			  test-2			\
 			  test-2-enter			\
 			  test-2-leave			\
 			  test-2-roundtrip
 
 cib-versions:
 	@echo "Max: $(CIB_max)"
 	@echo "Available: $(CIB_versions)"
 
 api-versions:
 	@echo "Max: $(API_max)"
 	@echo "Available: $(API_versions)"
 
 # Dynamically generated top-level API schema
 api/api-result.rng: api/api-result-$(API_max).rng
 	$(AM_V_at)$(MKDIR_P) api # might not exist in VPATH build
 	$(AM_V_SCHEMA)cp $(top_builddir)/xml/$< $@
 
 api/api-result-%.rng: $(API_build_copies) best-match.sh Makefile.am
 	$(AM_V_at)echo '<?xml version="1.0" encoding="UTF-8"?>' > $@
 	$(AM_V_at)echo '<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">' >> $@
 	$(AM_V_at)echo '  <start>' >> $@
 	$(AM_V_at)echo '    <element name="pacemaker-result">' >> $@
 	$(AM_V_at)echo '      <attribute name="api-version"> <text /> </attribute>' >> $@
 	$(AM_V_at)echo '      <attribute name="request"> <text /> </attribute>' >> $@
 	$(AM_V_at)echo '      <optional>' >> $@
 	$(AM_V_at)echo '        <choice>' >> $@
 	$(AM_V_at)for rng in $(API_request_base); do $(srcdir)/best-match.sh api/$$rng $(*) $(@) "          " || :; done
 	$(AM_V_at)echo '        </choice>' >> $@
 	$(AM_V_at)echo '      </optional>' >> $@
 	$(AM_V_at)$(srcdir)/best-match.sh api/status $(*) $(@) "      " || :
 	$(AM_V_at)echo '    </element>' >> $@
 	$(AM_V_at)echo '  </start>' >> $@
 	$(AM_V_SCHEMA)echo '</grammar>' >> $@
 
 crm_mon.rng: api/crm_mon-$(MON_max).rng
 	$(AM_V_at)echo '<?xml version="1.0" encoding="UTF-8"?>' > $@
 	$(AM_V_at)echo '<grammar xmlns="http://relaxng.org/ns/structure/1.0"' >> $@
 	$(AM_V_at)echo '         datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">' >> $@
 	$(AM_V_at)echo '    <start>' >> $@
 	$(AM_V_at)echo '        <ref name="element-crm_mon-old"/>' >> $@
 	$(AM_V_at)echo '    </start>' >> $@
 	$(AM_V_at)echo '    <define name="element-crm_mon-old">' >> $@
 	$(AM_V_at)echo '        <element name="crm_mon">' >> $@
 	$(AM_V_at)echo '            <attribute name="version"> <text/> </attribute>' >> $@
 	$(AM_V_at)echo '            <externalRef href="$(<)" />' >> $@
 	$(AM_V_at)echo '        </element>' >> $@
 	$(AM_V_at)echo '    </define>' >> $@
 	$(AM_V_SCHEMA)echo '</grammar>' >> $@
 
 # Dynamically generated top-level CIB schema
 pacemaker.rng: pacemaker-$(CIB_max).rng
 	$(AM_V_SCHEMA)cp $(top_builddir)/xml/$< $@
 
 pacemaker-%.rng: $(CIB_build_copies) best-match.sh Makefile.am
 	$(AM_V_at)echo '<?xml version="1.0" encoding="UTF-8"?>' > $@
 	$(AM_V_at)echo '<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">' >> $@
 	$(AM_V_at)echo '  <start>' >> $@
 	$(AM_V_at)echo '    <element name="cib">' >> $@
 	$(AM_V_at)$(srcdir)/best-match.sh cib $(*) $(@) "      "
 	$(AM_V_at)echo '      <element name="configuration">' >> $@
 	$(AM_V_at)echo '        <interleave>' >> $@
 	$(AM_V_at)for rng in $(CIB_cfg_base); do $(srcdir)/best-match.sh $$rng $(*) $(@) "          " || :; done
 	$(AM_V_at)echo '        </interleave>' >> $@
 	$(AM_V_at)echo '      </element>' >> $@
 	$(AM_V_at)echo '      <optional>' >> $@
 	$(AM_V_at)echo '        <element name="status">' >> $@
 	$(AM_V_at)$(srcdir)/best-match.sh status $(*) $(@) "          "
 	$(AM_V_at)echo '        </element>' >> $@
 	$(AM_V_at)echo '      </optional>' >> $@
 	$(AM_V_at)echo '    </element>' >> $@
 	$(AM_V_at)echo '  </start>' >> $@
 	$(AM_V_SCHEMA)echo '</grammar>' >> $@
 
 # Dynamically generated CIB schema listing all pacemaker versions
 versions.rng: Makefile.am
 	$(AM_V_at)echo '<?xml version="1.0" encoding="UTF-8"?>' > $@
 	$(AM_V_at)echo '<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">' >> $@
 	$(AM_V_at)echo '  <start>' >> $@
 	$(AM_V_at)echo '   <interleave>' >> $@
 	$(AM_V_at)echo '    <optional>' >> $@
 	$(AM_V_at)echo '      <attribute name="validate-with">' >> $@
 	$(AM_V_at)echo '        <choice>' >> $@
 	$(AM_V_at)echo '          <value>none</value>' >> $@
 	$(AM_V_at)echo '          <value>pacemaker-0.6</value>' >> $@
 	$(AM_V_at)echo '          <value>transitional-0.6</value>' >> $@
 	$(AM_V_at)echo '          <value>pacemaker-0.7</value>' >> $@
 	$(AM_V_at)echo '          <value>pacemaker-1.1</value>' >> $@
 	$(AM_V_at)for rng in $(CIB_versions); do echo "          <value>pacemaker-$$rng</value>" >> $@; done
 	$(AM_V_at)echo '        </choice>' >> $@
 	$(AM_V_at)echo '      </attribute>' >> $@
 	$(AM_V_at)echo '    </optional>' >> $@
 	$(AM_V_at)echo '    <attribute name="admin_epoch"><data type="nonNegativeInteger"/></attribute>' >> $@
 	$(AM_V_at)echo '    <attribute name="epoch"><data type="nonNegativeInteger"/></attribute>' >> $@
 	$(AM_V_at)echo '    <attribute name="num_updates"><data type="nonNegativeInteger"/></attribute>' >> $@
 	$(AM_V_at)echo '   </interleave>' >> $@
 	$(AM_V_at)echo '  </start>' >> $@
 	$(AM_V_SCHEMA)echo '</grammar>' >> $@
 
 # diff fails with ec=2 if no predecessor is found;
 # this uses '=' GNU extension to sed, if that's not available,
 # one can use: hline=`echo "$${p}" | grep -Fn "$${hunk}" | cut -d: -f1`;
 # XXX: use line information from hunk to avoid "not detected" for ambiguity
 version_diff = \
 	@for p in $(1); do \
 	  set `echo "$${p}" | tr '-' ' '`; \
 	  echo "\#\#\# *-$$2.rng vs. predecessor"; \
 	  for v in *-$$2.rng; do \
 	    echo "\#\#\#\# $${v} vs. predecessor"; b=`echo "$${v}" | cut -d- -f1`; \
 	    old=`./best-match.sh $${b} $$1`; \
 	    p=`diff -u "$${old}" "$${v}" 2>/dev/null`; \
 	    case $$? in \
 	    1) echo "$${p}" | sed -n -e '/^@@ /!d;=;p' \
 	       -e ':l;n;/^\([- ]\|+.*<[^ />]\+\([^/>]\+="ID\|>$$\)\)/bl;s/^[+ ]\(.*\)/\1/p' \
 	       | while read hline; do \
 	           read h && read i || break; \
 	           iline=`grep -Fn "$${i}" "$${v}" | cut -d: -f1`; \
 	           ctxt="(not detected)"; \
 	           if test `echo "$${iline}" | wc -l` -eq 1; then \
 	             ctxt=`{ sed -n -e "1,$$(($${iline}-1))p" "$${v}"; \
 	                     echo "<inject id=\"GOAL\"/>$${i}"; \
 	                     sed -n -e "$$(($${iline}+1)),$$ p" "$${v}"; \
 	                   } | $(XSLTPROC) --param skip 1 context-of.xsl -`; \
 	           fi; \
 	           echo "$${p}" | sed -n -e "$$(($${hline}-2)),$${hline}!d" \
 	             -e '/^\(+++\|---\)/p'; \
 	           echo "$${h} context: $${ctxt}"; \
 	           echo "$${p}" | sed -n -e "1,$${hline}d" \
 	             -e '/^\(---\|@@ \)/be;p;d;:e;n;be'; \
 	           done; \
 	       ;; \
 	    2) echo "\#\#\#\#\# $${v} has no predecessor";; \
 	    esac; \
 	  done; \
 	done
 
 diff: best-match.sh
 	@echo "#  Comparing changes in + since $(CIB_max)"
 	$(call version_diff,${CIB_version_pairs_last})
 
 fulldiff: best-match.sh
 	@echo "#  Comparing all changes across all the subsequent increments"
 	$(call version_diff,${CIB_version_pairs})
 
 CLEANFILES = $(API_generated) $(CIB_generated) $(MON_generated)
 
 # Remove pacemaker schema files generated by *any* source version. This allows
 # "make -C xml clean" to have the desired effect when checking out an earlier
 # revision in a source tree.
 clean-local:
 	if [ "x$(srcdir)" != "x$(builddir)" ]; then					\
 		rm -f $(API_build_copies) $(CIB_build_copies) $(MON_build_copies);	\
 	fi
 	rm -f $(builddir)/pacemaker-[0-9]*.[0-9]*.rng
 
 # Enable ability to use $@ in prerequisite
 .SECONDEXPANSION:
 
 # For VPATH builds, copy the static schema files into the build tree
 $(API_build_copies) $(CIB_build_copies) $(MON_build_copies): $$(subst $(abs_builddir),$(srcdir),$$(@))
 	$(AM_V_GEN)if [ "x$(srcdir)" != "x$(builddir)" ]; then	\
 		$(MKDIR_P) "$(dir $(@))";			\
 		cp "$(<)" "$(@)";				\
 	fi
diff --git a/xml/api/crm_mon-2.12.rng b/xml/api/crm_mon-2.12.rng
new file mode 100644
index 0000000000..be14412b06
--- /dev/null
+++ b/xml/api/crm_mon-2.12.rng
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+         datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+
+    <start>
+        <ref name="element-crm-mon"/>
+    </start>
+
+    <define name="element-crm-mon">
+        <optional>
+            <ref name="element-summary" />
+        </optional>
+        <optional>
+            <ref name="nodes-list" />
+        </optional>
+        <optional>
+            <ref name="resources-list" />
+        </optional>
+        <optional>
+            <ref name="node-attributes-list" />
+        </optional>
+        <optional>
+            <externalRef href="node-history-2.12.rng"/>
+        </optional>
+        <optional>
+            <ref name="failures-list" />
+        </optional>
+        <optional>
+            <ref name="fence-event-list" />
+        </optional>
+        <optional>
+            <ref name="tickets-list" />
+        </optional>
+        <optional>
+            <ref name="bans-list" />
+        </optional>
+    </define>
+
+    <define name="element-summary">
+        <element name="summary">
+            <optional>
+                <element name="stack">
+                    <attribute name="type"> <text /> </attribute>
+                </element>
+            </optional>
+            <optional>
+                <element name="current_dc">
+                    <attribute name="present"> <data type="boolean" /> </attribute>
+                    <optional>
+                        <group>
+                            <attribute name="version"> <text /> </attribute>
+                            <attribute name="name"> <text /> </attribute>
+                            <attribute name="id"> <text /> </attribute>
+                            <attribute name="with_quorum"> <data type="boolean" /> </attribute>
+                        </group>
+                    </optional>
+                </element>
+            </optional>
+            <optional>
+                <element name="last_update">
+                    <attribute name="time"> <text /> </attribute>
+                </element>
+                <element name="last_change">
+                    <attribute name="time"> <text /> </attribute>
+                    <attribute name="user"> <text /> </attribute>
+                    <attribute name="client"> <text /> </attribute>
+                    <attribute name="origin"> <text /> </attribute>
+                </element>
+            </optional>
+            <optional>
+                <element name="nodes_configured">
+                    <attribute name="number"> <data type="nonNegativeInteger" /> </attribute>
+                </element>
+                <element name="resources_configured">
+                    <attribute name="number"> <data type="nonNegativeInteger" /> </attribute>
+                    <attribute name="disabled"> <data type="nonNegativeInteger" /> </attribute>
+                    <attribute name="blocked"> <data type="nonNegativeInteger" /> </attribute>
+                </element>
+            </optional>
+            <optional>
+                <element name="cluster_options">
+                    <attribute name="stonith-enabled"> <data type="boolean" /> </attribute>
+                    <attribute name="symmetric-cluster"> <data type="boolean" /> </attribute>
+                    <attribute name="no-quorum-policy"> <text /> </attribute>
+                    <attribute name="maintenance-mode"> <data type="boolean" /> </attribute>
+                    <attribute name="stop-all-resources"> <data type="boolean" /> </attribute>
+                </element>
+            </optional>
+        </element>
+    </define>
+
+    <define name="resources-list">
+        <element name="resources">
+            <zeroOrMore>
+                <externalRef href="resources-2.4.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="nodes-list">
+        <element name="nodes">
+            <zeroOrMore>
+                <externalRef href="nodes-2.8.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="node-attributes-list">
+        <element name="node_attributes">
+            <zeroOrMore>
+                <externalRef href="node-attrs-2.8.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="failures-list">
+        <element name="failures">
+            <zeroOrMore>
+                <externalRef href="failure-2.8.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="fence-event-list">
+        <element name="fence_history">
+            <optional>
+                <attribute name="status"> <data type="integer" /> </attribute>
+            </optional>
+            <zeroOrMore>
+                <externalRef href="fence-event-2.0.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="tickets-list">
+        <element name="tickets">
+            <zeroOrMore>
+                <ref name="element-ticket" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="bans-list">
+        <element name="bans">
+            <zeroOrMore>
+                <ref name="element-ban" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="element-ticket">
+        <element name="ticket">
+            <attribute name="id"> <text /> </attribute>
+            <attribute name="status">
+                <choice>
+                    <value>granted</value>
+                    <value>revoked</value>
+                </choice>
+            </attribute>
+            <attribute name="standby"> <data type="boolean" /> </attribute>
+            <optional>
+                <attribute name="last-granted"> <text /> </attribute>
+            </optional>
+        </element>
+    </define>
+
+    <define name="element-ban">
+        <element name="ban">
+            <attribute name="id"> <text /> </attribute>
+            <attribute name="resource"> <text /> </attribute>
+            <attribute name="node"> <text /> </attribute>
+            <attribute name="weight"> <data type="integer" /> </attribute>
+            <attribute name="promoted-only"> <data type="boolean" /> </attribute>
+            <!-- DEPRECATED: master_only is a duplicate of promoted-only that is
+                 provided solely for API backward compatibility. It will be
+                 removed in a future release. Check promoted-only instead.
+              -->
+            <attribute name="master_only"> <data type="boolean" /> </attribute>
+        </element>
+    </define>
+</grammar>
diff --git a/xml/api/crm_simulate-2.12.rng b/xml/api/crm_simulate-2.12.rng
new file mode 100644
index 0000000000..f90bd36de4
--- /dev/null
+++ b/xml/api/crm_simulate-2.12.rng
@@ -0,0 +1,338 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+         datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+
+    <start>
+        <ref name="element-crm-simulate"/>
+    </start>
+
+    <define name="element-crm-simulate">
+        <choice>
+            <ref name="timings-list" />
+            <group>
+                <ref name="cluster-status" />
+                <optional>
+                    <ref name="modifications-list" />
+                </optional>
+                <optional>
+                    <ref name="allocations-utilizations-list" />
+                </optional>
+                <optional>
+                    <ref name="action-list" />
+                </optional>
+                <optional>
+                    <ref name="cluster-injected-actions-list" />
+                    <ref name="revised-cluster-status" />
+                </optional>
+            </group>
+        </choice>
+    </define>
+
+    <define name="allocations-utilizations-list">
+        <choice>
+            <element name="allocations">
+                <zeroOrMore>
+                    <choice>
+                        <ref name="element-allocation" />
+                        <ref name="element-promotion" />
+                    </choice>
+                </zeroOrMore>
+            </element>
+            <element name="utilizations">
+                <zeroOrMore>
+                    <choice>
+                        <ref name="element-capacity" />
+                        <ref name="element-utilization" />
+                    </choice>
+                </zeroOrMore>
+            </element>
+            <element name="allocations_utilizations">
+                <zeroOrMore>
+                    <choice>
+                        <ref name="element-allocation" />
+                        <ref name="element-promotion" />
+                        <ref name="element-capacity" />
+                        <ref name="element-utilization" />
+                    </choice>
+                </zeroOrMore>
+            </element>
+        </choice>
+    </define>
+
+    <define name="cluster-status">
+        <element name="cluster_status">
+            <ref name="nodes-list" />
+            <ref name="resources-list" />
+            <optional>
+                <ref name="node-attributes-list" />
+            </optional>
+            <optional>
+                <externalRef href="node-history-2.12.rng" />
+            </optional>
+            <optional>
+                <ref name="failures-list" />
+            </optional>
+        </element>
+    </define>
+
+    <define name="modifications-list">
+        <element name="modifications">
+            <optional>
+                <attribute name="quorum"> <text /> </attribute>
+            </optional>
+            <optional>
+                <attribute name="watchdog"> <text /> </attribute>
+            </optional>
+            <zeroOrMore>
+                <ref name="element-inject-modify-node" />
+            </zeroOrMore>
+            <zeroOrMore>
+                <ref name="element-inject-modify-ticket" />
+            </zeroOrMore>
+            <zeroOrMore>
+                <ref name="element-inject-spec" />
+            </zeroOrMore>
+            <zeroOrMore>
+                <ref name="element-inject-attr" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="revised-cluster-status">
+        <element name="revised_cluster_status">
+            <ref name="nodes-list" />
+            <ref name="resources-list" />
+            <optional>
+                <ref name="node-attributes-list" />
+            </optional>
+            <optional>
+                <ref name="failures-list" />
+            </optional>
+        </element>
+    </define>
+
+    <define name="element-inject-attr">
+        <element name="inject_attr">
+            <attribute name="cib_node"> <text /> </attribute>
+            <attribute name="name"> <text /> </attribute>
+            <attribute name="node_path"> <text /> </attribute>
+            <attribute name="value"> <text /> </attribute>
+        </element>
+    </define>
+
+    <define name="element-inject-modify-node">
+        <element name="modify_node">
+            <attribute name="action"> <text /> </attribute>
+            <attribute name="node"> <text /> </attribute>
+        </element>
+    </define>
+
+    <define name="element-inject-spec">
+        <element name="inject_spec">
+            <attribute name="spec"> <text /> </attribute>
+        </element>
+    </define>
+
+    <define name="element-inject-modify-ticket">
+        <element name="modify_ticket">
+            <attribute name="action"> <text /> </attribute>
+            <attribute name="ticket"> <text /> </attribute>
+        </element>
+    </define>
+
+    <define name="cluster-injected-actions-list">
+        <element name="transition">
+            <zeroOrMore>
+                <ref name="element-injected-actions" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="node-attributes-list">
+        <element name="node_attributes">
+            <zeroOrMore>
+                <externalRef href="node-attrs-2.8.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="failures-list">
+        <element name="failures">
+            <zeroOrMore>
+                <externalRef href="failure-2.8.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="nodes-list">
+        <element name="nodes">
+            <zeroOrMore>
+                <externalRef href="nodes-2.8.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="resources-list">
+        <element name="resources">
+            <zeroOrMore>
+                <externalRef href="resources-2.4.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="timings-list">
+        <element name="timings">
+            <zeroOrMore>
+                <ref name="element-timing" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="action-list">
+        <element name="actions">
+            <zeroOrMore>
+                <ref name="element-node-action" />
+            </zeroOrMore>
+            <zeroOrMore>
+                <ref name="element-rsc-action" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="element-allocation">
+        <element name="node_weight">
+            <attribute name="function"> <text /> </attribute>
+            <attribute name="node"> <text /> </attribute>
+            <externalRef href="../score.rng" />
+            <optional>
+                <attribute name="id"> <text /> </attribute>
+            </optional>
+        </element>
+    </define>
+
+    <define name="element-capacity">
+        <element name="capacity">
+            <attribute name="comment"> <text /> </attribute>
+            <attribute name="node"> <text /> </attribute>
+            <zeroOrMore>
+                <element>
+                    <anyName />
+                    <text />
+                </element>
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="element-inject-cluster-action">
+        <element name="cluster_action">
+            <attribute name="node"> <text /> </attribute>
+            <attribute name="task"> <text /> </attribute>
+            <optional>
+                <attribute name="id"> <text /> </attribute>
+            </optional>
+        </element>
+    </define>
+
+    <define name="element-injected-actions">
+        <choice>
+            <ref name="element-inject-cluster-action" />
+            <ref name="element-inject-fencing-action" />
+            <ref name="element-inject-pseudo-action" />
+            <ref name="element-inject-rsc-action" />
+        </choice>
+    </define>
+
+    <define name="element-inject-fencing-action">
+        <element name="fencing_action">
+            <attribute name="op"> <text /> </attribute>
+            <attribute name="target"> <text /> </attribute>
+        </element>
+    </define>
+
+    <define name="element-node-action">
+        <element name="node_action">
+            <attribute name="node"> <text /> </attribute>
+            <attribute name="reason"> <text /> </attribute>
+            <attribute name="task"> <text /> </attribute>
+        </element>
+    </define>
+
+    <define name="element-promotion">
+        <element name="promotion_score">
+            <attribute name="id"> <text /> </attribute>
+            <externalRef href="../score.rng" />
+            <optional>
+                <attribute name="node"> <text /> </attribute>
+            </optional>
+        </element>
+    </define>
+
+    <define name="element-inject-pseudo-action">
+        <element name="pseudo_action">
+            <attribute name="task"> <text /> </attribute>
+            <optional>
+                <attribute name="node"> <text /> </attribute>
+            </optional>
+        </element>
+    </define>
+
+    <define name="element-inject-rsc-action">
+        <element name="rsc_action">
+            <attribute name="node"> <text /> </attribute>
+            <attribute name="op"> <text /> </attribute>
+            <attribute name="resource"> <text /> </attribute>
+            <optional>
+                <attribute name="interval"> <data type="integer" /> </attribute>
+            </optional>
+        </element>
+    </define>
+
+    <define name="element-timing">
+        <element name="timing">
+            <attribute name="file"> <text /> </attribute>
+            <attribute name="duration"> <data type="double" /> </attribute>
+        </element>
+    </define>
+
+    <define name="element-rsc-action">
+        <element name="rsc_action">
+            <attribute name="action"> <text /> </attribute>
+            <attribute name="resource"> <text /> </attribute>
+            <optional>
+                <attribute name="blocked"> <data type="boolean" /> </attribute>
+            </optional>
+            <optional>
+                <attribute name="dest"> <text /> </attribute>
+            </optional>
+            <optional>
+                <attribute name="next-role"> <text /> </attribute>
+            </optional>
+            <optional>
+                <attribute name="node"> <text /> </attribute>
+            </optional>
+            <optional>
+                <attribute name="reason"> <text /> </attribute>
+            </optional>
+            <optional>
+                <attribute name="role"> <text /> </attribute>
+            </optional>
+            <optional>
+                <attribute name="source"> <text /> </attribute>
+            </optional>
+        </element>
+    </define>
+
+    <define name="element-utilization">
+        <element name="utilization">
+            <attribute name="function"> <text /> </attribute>
+            <attribute name="node"> <text /> </attribute>
+            <attribute name="resource"> <text /> </attribute>
+            <zeroOrMore>
+                <element>
+                    <anyName />
+                    <text />
+                </element>
+            </zeroOrMore>
+        </element>
+    </define>
+</grammar>
diff --git a/xml/api/node-history-2.12.rng b/xml/api/node-history-2.12.rng
new file mode 100644
index 0000000000..9628000ecb
--- /dev/null
+++ b/xml/api/node-history-2.12.rng
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+         datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+
+    <start>
+        <ref name="node-history-list" />
+    </start>
+
+    <define name="node-history-list">
+        <element name="node_history">
+            <zeroOrMore>
+                <ref name="element-node-history" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="element-node-history">
+        <element name="node">
+            <attribute name="name"> <text /> </attribute>
+            <zeroOrMore>
+                <ref name="element-resource-history" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="element-resource-history">
+        <element name="resource_history">
+            <attribute name="id"> <text /> </attribute>
+            <attribute name="orphan"> <data type="boolean" /> </attribute>
+            <optional>
+                <group>
+                    <attribute name="migration-threshold"> <data type="nonNegativeInteger" /> </attribute>
+                    <optional>
+                        <attribute name="fail-count"> <text /> </attribute>
+                    </optional>
+                    <optional>
+                        <attribute name="last-failure"> <text /> </attribute>
+                    </optional>
+                </group>
+            </optional>
+            <zeroOrMore>
+                <ref name="element-operation-history" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="element-operation-history">
+        <element name="operation_history">
+            <attribute name="call"> <text /> </attribute>
+            <attribute name="task"> <text /> </attribute>
+            <optional>
+                <attribute name="interval"> <text /> </attribute>
+            </optional>
+            <optional>
+                <attribute name="last-rc-change"> <text /> </attribute>
+            </optional>
+            <optional>
+                <attribute name="last-run"> <text /> </attribute>
+            </optional>
+            <optional>
+                <attribute name="exec-time"> <text /> </attribute>
+            </optional>
+            <optional>
+                <attribute name="queue-time"> <text /> </attribute>
+            </optional>
+            <attribute name="rc"> <data type="integer" /> </attribute>
+            <attribute name="rc_text"> <text /> </attribute>
+        </element>
+    </define>
+</grammar>