diff --git a/include/crm/common/xml.h b/include/crm/common/xml.h
index a42479295b..519fd88912 100644
--- a/include/crm/common/xml.h
+++ b/include/crm/common/xml.h
@@ -1,80 +1,79 @@
 /*
  * Copyright 2004-2025 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #ifndef PCMK__CRM_COMMON_XML__H
 #define PCMK__CRM_COMMON_XML__H
 
 
 #include <stdio.h>
 #include <sys/types.h>
 #include <unistd.h>
 
 #include <stdlib.h>
 #include <errno.h>
 #include <fcntl.h>
 
 #include <libxml/tree.h>
 #include <libxml/xpath.h>
 
 #include <crm/crm.h>
 #include <crm/common/nvpair.h>
 #include <crm/common/schemas.h>
 #include <crm/common/xml_io.h>
 #include <crm/common/xml_names.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /**
  * \file
  * \brief Wrappers for and extensions to libxml2
  * \ingroup core
  */
 
 typedef const xmlChar *pcmkXmlStr;
 
 
 /*
  * Searching & Modifying
  */
 
 // NOTE: sbd (as of at least 1.5.2) uses this
 xmlNode *get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level);
 
 void crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
                               void (*helper)(xmlNode*, void*), void *user_data);
 
 void freeXpathObject(xmlXPathObjectPtr xpathObj);
-xmlNode *getXpathResult(xmlXPathObjectPtr xpathObj, int index);
 void dedupXpathResults(xmlXPathObjectPtr xpathObj);
 
 bool xml_tracking_changes(xmlNode * xml);
 bool xml_document_dirty(xmlNode *xml);
 void xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls);
 void xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml);
 void xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml);
 void xml_accept_changes(xmlNode * xml);
 bool xml_patch_versions(const xmlNode *patchset, int add[3], int del[3]);
 
 xmlNode *xml_create_patchset(
     int format, xmlNode *source, xmlNode *target, bool *config, bool manage_version);
 int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version);
 
 void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest);
 
 #ifdef __cplusplus
 }
 #endif
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
 #include <crm/common/xml_compat.h>
 #endif
 
 #endif
diff --git a/include/crm/common/xml_compat.h b/include/crm/common/xml_compat.h
index 5b726422df..774423e11a 100644
--- a/include/crm/common/xml_compat.h
+++ b/include/crm/common/xml_compat.h
@@ -1,92 +1,95 @@
 /*
  * Copyright 2004-2025 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #ifndef PCMK__CRM_COMMON_XML_COMPAT__H
 #define PCMK__CRM_COMMON_XML_COMPAT__H
 
 #include <glib.h>               // gboolean
 #include <libxml/tree.h>        // xmlNode
 #include <libxml/xpath.h>           // xmlXPathObject
 
 #include <crm/common/nvpair.h>  // crm_xml_add()
 #include <crm/common/xml_names.h>   // PCMK_XE_CLONE
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /**
  * \file
  * \brief Deprecated Pacemaker XML API
  * \ingroup core
  * \deprecated Do not include this header directly. The XML APIs in this
  *             header, and the header itself, will be removed in a future
  *             release.
  */
 
 // NOTE: sbd (as of at least 1.5.2) uses this
 //! \deprecated Use name member directly
 static inline const char *
 crm_element_name(const xmlNode *xml)
 {
     return (xml == NULL)? NULL : (const char *) xml->name;
 }
 
 // NOTE: sbd (as of at least 1.5.2) uses this
 //! \deprecated Do not use Pacemaker for general-purpose XML manipulation
 xmlNode *copy_xml(xmlNode *src_node);
 
 // NOTE: sbd (as of at least 1.5.2) uses this
 //! \deprecated Do not use
 gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs);
 
 // NOTE: sbd (as of at least 1.5.2) uses this
 //! \deprecated Call \c crm_log_init() or \c crm_log_cli_init() instead
 void crm_xml_init(void);
 
 //! \deprecated Exit with \c crm_exit() instead
 void crm_xml_cleanup(void);
 
 //! \deprecated Do not use Pacemaker for general-purpose XML manipulation
 void pcmk_free_xml_subtree(xmlNode *xml);
 
 // NOTE: sbd (as of at least 1.5.2) uses this
 //! \deprecated Do not use Pacemaker for general-purpose XML manipulation
 void free_xml(xmlNode *child);
 
 //! \deprecated Do not use Pacemaker for general-purpose XML manipulation
 void crm_xml_sanitize_id(char *id);
 
 //! \deprecated Do not use
 char *calculate_on_disk_digest(xmlNode *input);
 
 //! \deprecated Do not use
 char *calculate_operation_digest(xmlNode *input, const char *version);
 
 //! \deprecated Do not use
 char *calculate_xml_versioned_digest(xmlNode *input, gboolean sort,
                                      gboolean do_filter, const char *version);
 
 //! \deprecated Do not use
 xmlXPathObjectPtr xpath_search(const xmlNode *xml_top, const char *path);
 
 //! \deprecated Do not use
 static inline int numXpathResults(xmlXPathObjectPtr xpathObj)
 {
     if ((xpathObj == NULL) || (xpathObj->nodesetval == NULL)) {
         return 0;
     }
     return xpathObj->nodesetval->nodeNr;
 }
 
+//! \deprecated Do not use
+xmlNode *getXpathResult(xmlXPathObjectPtr xpathObj, int index);
+
 #ifdef __cplusplus
 }
 #endif
 
 #endif // PCMK__CRM_COMMON_XML_COMPAT__H
diff --git a/lib/common/xpath.c b/lib/common/xpath.c
index a3d60d28dc..9fe1848146 100644
--- a/lib/common/xpath.c
+++ b/lib/common/xpath.c
@@ -1,510 +1,510 @@
 /*
  * Copyright 2004-2025 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 #include <stdio.h>
 #include <string.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 #include "crmcommon_private.h"
 
 #include <libxml/xpath.h>               // xmlXPathObject, etc.
 
 /*
  * From xpath2.c
  *
  * All the elements returned by an XPath query are pointers to
  * elements from the tree *except* namespace nodes where the XPath
  * semantic is different from the implementation in libxml2 tree.
  * As a result when a returned node set is freed when
  * xmlXPathFreeObject() is called, that routine must check the
  * element type. But node from the returned set may have been removed
  * by xmlNodeSetContent() resulting in access to freed data.
  *
  * This can be exercised by running
  *       valgrind xpath2 test3.xml '//discarded' discarded
  *
  * There is 2 ways around it:
  *   - make a copy of the pointers to the nodes from the result set
  *     then call xmlXPathFreeObject() and then modify the nodes
  * or
  * - remove the references from the node set, if they are not
        namespace nodes, before calling xmlXPathFreeObject().
  */
 void
 freeXpathObject(xmlXPathObjectPtr xpathObj)
 {
     int max = pcmk__xpath_num_results(xpathObj);
 
     if (xpathObj == NULL) {
         return;
     }
 
     for (int lpc = 0; lpc < max; lpc++) {
         if (xpathObj->nodesetval->nodeTab[lpc] && xpathObj->nodesetval->nodeTab[lpc]->type != XML_NAMESPACE_DECL) {
             xpathObj->nodesetval->nodeTab[lpc] = NULL;
         }
     }
 
     /* _Now_ it's safe to free it */
     xmlXPathFreeObject(xpathObj);
 }
 
 
 /*!
  * \internal
  * \brief Get a node from the result set of evaluating an XPath expression
  *
  * Evaluating an XPath expression stores the list of matching nodes in an
  * \c xmlXPathObject. This function gets the node at a particular index within
  * that list.
  *
  * \param[in,out] xpath_obj  XPath object containing result nodes
  * \param[in]     index      Index of result node to get
  *
  * \return Result node at the given index if possible, or \c NULL otherwise
  *
  * \note This has a side effect: it sets the result node at \p index to NULL
  *       within \p xpath_obj, so the result at a given index can be retrieved
  *       only once. This is a workaround to prevent a use-after-free error.
  *
  *       All elements returned by an XPath query are pointers to elements from
  *       the tree, except namespace nodes (which are allocated separately for
  *       the XPath object's node set). Accordingly, only namespace nodes and the
  *       node set itself are freed when libxml2 frees a node set.
  *
  *       This logic requires checking the type of every node in the node set.
  *       However, a node may have been freed already while processing an XPath
  *       object -- either directly (for example, with \c xmlFreeNode()) or
  *       indirectly (for example, with \c xmlNodeSetContent()). In that case,
  *       checking the freed node's type while freeing the XPath object is a
  *       use-after-free error.
  *
  *       To reduce the likelihood of this, when we access a node in the XPath
  *       object, we remove it from the XPath object's node set by setting it to
  *       \c NULL. This approach is adapted from \c xpath2.c in libxml2's
  *       examples. That file also describes a way to reproduce the
  *       use-after-free error.
  *
  *       However, there are still ways that a use-after-free can occur. For
  *       example, freeing the entire XML tree before freeing an XPath object
  *       that contains pointers to it would be an error. It's dangerous to mix
  *       processing XPath search results with modifications to a tree, and it
  *       must be done with care.
  */
 xmlNode *
 pcmk__xpath_result(xmlXPathObject *xpath_obj, int index)
 {
     xmlNode *match = NULL;
 
     CRM_CHECK((xpath_obj != NULL) && (index >= 0), return NULL);
 
     match = xmlXPathNodeSetItem(xpath_obj->nodesetval, index);
     if (match == NULL) {
         // Previously requested or out of range
         return NULL;
     }
 
     if (match->type != XML_NAMESPACE_DECL) {
         xpath_obj->nodesetval->nodeTab[index] = NULL;
     }
 
     return match;
 }
 
 /*!
  * \internal
  * \brief Get an element node corresponding to an XPath match node
  *
  * Each node in an XPath object's result node set may be of an arbitrary type.
  * This function is guaranteed to return an element node (or \c NULL).
  *
  * \param[in] match  XML node that matched some XPath expression
  *
  * \retval \p match if \p match is an element
  * \retval Root element of \p match if \p match is a document
  * \retval <tt>match->parent</tt> if \p match is not an element but its parent
  *         is an element
  * \retval \c NULL otherwise
  *
  * \todo Phase this out. Code that relies on this behavior is likely buggy.
  */
 xmlNode *
 pcmk__xpath_match_element(xmlNode *match)
 {
     pcmk__assert(match != NULL);
 
     switch (match->type) {
         case XML_ELEMENT_NODE:
             return match;
 
         case XML_DOCUMENT_NODE:
             // Happens if XPath expression is "/"; return root element instead
             return xmlDocGetRootElement((xmlDoc *) match);
 
         default:
             if ((match->parent != NULL)
                 && (match->parent->type == XML_ELEMENT_NODE)) {
 
                 // Probably an attribute; return parent element instead
                 return match->parent;
             }
             crm_err("Cannot get element from XPath expression match of type %s",
                     pcmk__xml_element_type_text(match->type));
             return NULL;
     }
 }
 
-xmlNode *
-getXpathResult(xmlXPathObjectPtr xpathObj, int index)
-{
-    xmlNode *match = NULL;
-    int max = pcmk__xpath_num_results(xpathObj);
-
-    CRM_CHECK(index >= 0, return NULL);
-    CRM_CHECK(xpathObj != NULL, return NULL);
-
-    if (index >= max) {
-        crm_err("Requested index %d of only %d items", index, max);
-        return NULL;
-
-    } else if(xpathObj->nodesetval->nodeTab[index] == NULL) {
-        /* Previously requested */
-        return NULL;
-    }
-
-    match = xpathObj->nodesetval->nodeTab[index];
-    CRM_CHECK(match != NULL, return NULL);
-
-    if (xpathObj->nodesetval->nodeTab[index]->type != XML_NAMESPACE_DECL) {
-        // See the comment for pcmk__xpath_result()
-        xpathObj->nodesetval->nodeTab[index] = NULL;
-    }
-
-    switch (match->type) {
-        case XML_ELEMENT_NODE:
-            return match;
-
-        case XML_DOCUMENT_NODE: // Searched for '/'
-            return match->children;
-
-        default:
-           if ((match->parent != NULL)
-               && (match->parent->type == XML_ELEMENT_NODE)) {
-                return match->parent;
-           }
-           crm_warn("Unsupported XPath match type %d (bug?)", match->type);
-           return NULL;
-    }
-}
-
 void
 dedupXpathResults(xmlXPathObjectPtr xpathObj)
 {
     int max = pcmk__xpath_num_results(xpathObj);
 
     if (xpathObj == NULL) {
         return;
     }
 
     for (int lpc = 0; lpc < max; lpc++) {
         xmlNode *xml = NULL;
         gboolean dedup = FALSE;
 
         if (xpathObj->nodesetval->nodeTab[lpc] == NULL) {
             continue;
         }
 
         xml = xpathObj->nodesetval->nodeTab[lpc]->parent;
 
         for (; xml; xml = xml->parent) {
             int lpc2 = 0;
 
             for (lpc2 = 0; lpc2 < max; lpc2++) {
                 if (xpathObj->nodesetval->nodeTab[lpc2] == xml) {
                     xpathObj->nodesetval->nodeTab[lpc] = NULL;
                     dedup = TRUE;
                     break;
                 }
             }
 
             if (dedup) {
                 break;
             }
         }
     }
 }
 
 /*!
  * \internal
  * \brief Search an XML document using an XPath expression
  *
  * \param[in] doc   XML document to search
  * \param[in] path  XPath expression to evaluate in the context of \p doc
  *
  * \return XPath object containing result of evaluating \p path against \p doc
  */
 xmlXPathObject *
 pcmk__xpath_search(xmlDoc *doc, const char *path)
 {
     pcmkXmlStr xpath_expr = (pcmkXmlStr) path;
     xmlXPathContext *xpath_context = NULL;
     xmlXPathObject *xpath_obj = NULL;
 
     CRM_CHECK((doc != NULL) && !pcmk__str_empty(path), return NULL);
 
     xpath_context = xmlXPathNewContext(doc);
     pcmk__mem_assert(xpath_context);
 
     xpath_obj = xmlXPathEval(xpath_expr, xpath_context);
 
     xmlXPathFreeContext(xpath_context);
     return xpath_obj;
 }
 
 /*!
  * \brief Run a supplied function for each result of an xpath search
  *
  * \param[in,out] xml        XML to search
  * \param[in]     xpath      XPath search string
  * \param[in]     helper     Function to call for each result
  * \param[in,out] user_data  Data to pass to supplied function
  *
  * \note The helper function will be passed the XML node of the result,
  *       and the supplied user_data. This function does not otherwise
  *       use user_data.
  */
 void
 crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
                          void (*helper)(xmlNode*, void*), void *user_data)
 {
     xmlXPathObject *xpathObj = NULL;
     int nresults = 0;
 
     CRM_CHECK(xml != NULL, return);
 
     xpathObj = pcmk__xpath_search(xml->doc, xpath);
     nresults = pcmk__xpath_num_results(xpathObj);
 
     for (int i = 0; i < nresults; i++) {
         xmlNode *result = pcmk__xpath_result(xpathObj, i);
 
         CRM_LOG_ASSERT(result != NULL);
 
         if (result != NULL) {
             result = pcmk__xpath_match_element(result);
 
             CRM_LOG_ASSERT(result != NULL);
 
             if (result != NULL) {
                 (*helper)(result, user_data);
             }
         }
     }
     freeXpathObject(xpathObj);
 }
 
 xmlNode *
 get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level)
 {
     int max;
     xmlNode *result = NULL;
     xmlXPathObject *xpathObj = NULL;
     char *nodePath = NULL;
     char *matchNodePath = NULL;
 
     if (xpath == NULL) {
         return xml_obj;         /* or return NULL? */
     }
 
     xpathObj = pcmk__xpath_search(xml_obj->doc, xpath);
     nodePath = (char *)xmlGetNodePath(xml_obj);
     max = pcmk__xpath_num_results(xpathObj);
 
     if (max < 1) {
         if (error_level < LOG_NEVER) {
             do_crm_log(error_level, "No match for %s in %s",
                        xpath, pcmk__s(nodePath, "unknown path"));
             crm_log_xml_explicit(xml_obj, "Unexpected Input");
         }
 
     } else if (max > 1) {
         if (error_level < LOG_NEVER) {
             int lpc = 0;
 
             do_crm_log(error_level, "Too many matches for %s in %s",
                        xpath, pcmk__s(nodePath, "unknown path"));
 
             for (lpc = 0; lpc < max; lpc++) {
                 xmlNode *match = pcmk__xpath_result(xpathObj, lpc);
 
                 CRM_LOG_ASSERT(match != NULL);
                 if (match != NULL) {
                     match = pcmk__xpath_match_element(match);
 
                     CRM_LOG_ASSERT(match != NULL);
                     if (match != NULL) {
                         matchNodePath = (char *) xmlGetNodePath(match);
                         do_crm_log(error_level, "%s[%d] = %s",
                                    xpath, lpc,
                                    pcmk__s(matchNodePath,
                                            "unrecognizable match"));
                         free(matchNodePath);
                     }
                 }
             }
             crm_log_xml_explicit(xml_obj, "Bad Input");
         }
 
     } else {
         result = pcmk__xpath_result(xpathObj, 0);
         if (result != NULL) {
             result = pcmk__xpath_match_element(result);
         }
     }
 
     freeXpathObject(xpathObj);
     free(nodePath);
 
     return result;
 }
 
 /*!
  * \internal
  * \brief Get an XPath string that matches an XML element as closely as possible
  *
  * \param[in] xml  The XML element for which to build an XPath string
  *
  * \return A \p GString that matches \p xml, or \p NULL if \p xml is \p NULL.
  *
  * \note The caller is responsible for freeing the string using
  *       \p g_string_free().
  */
 GString *
 pcmk__element_xpath(const xmlNode *xml)
 {
     const xmlNode *parent = NULL;
     GString *xpath = NULL;
     const char *id = NULL;
 
     if (xml == NULL) {
         return NULL;
     }
 
     parent = xml->parent;
     xpath = pcmk__element_xpath(parent);
     if (xpath == NULL) {
         xpath = g_string_sized_new(256);
     }
 
     // Build xpath like "/" -> "/cib" -> "/cib/configuration"
     if (parent == NULL) {
         g_string_append_c(xpath, '/');
     } else if (parent->parent == NULL) {
         g_string_append(xpath, (const gchar *) xml->name);
     } else {
         pcmk__g_strcat(xpath, "/", (const char *) xml->name, NULL);
     }
 
     id = pcmk__xe_id(xml);
     if (id != NULL) {
         pcmk__g_strcat(xpath, "[@" PCMK_XA_ID "='", id, "']", NULL);
     }
 
     return xpath;
 }
 
 /*!
  * \internal
  * \brief Extract the ID attribute from an XML element
  *
  * \param[in] xpath String to search
  * \param[in] node  Node to get the ID for
  *
  * \return ID attribute of \p node in xpath string \p xpath
  */
 char *
 pcmk__xpath_node_id(const char *xpath, const char *node)
 {
     char *retval = NULL;
     char *patt = NULL;
     char *start = NULL;
     char *end = NULL;
 
     if (node == NULL || xpath == NULL) {
         return retval;
     }
 
     patt = crm_strdup_printf("/%s[@" PCMK_XA_ID "=", node);
     start = strstr(xpath, patt);
 
     if (!start) {
         free(patt);
         return retval;
     }
 
     start += strlen(patt);
     start++;
 
     end = strstr(start, "\'");
     pcmk__assert(end != NULL);
     retval = strndup(start, end-start);
 
     free(patt);
     return retval;
 }
 
 static int
 output_attr_child(xmlNode *child, void *userdata)
 {
     pcmk__output_t *out = userdata;
 
     out->info(out, "  Value: %s \t(id=%s)",
               crm_element_value(child, PCMK_XA_VALUE),
               pcmk__s(pcmk__xe_id(child), "<none>"));
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Warn if an XPath query returned multiple nodes with the same ID
  *
  * \param[in,out] out     Output object
  * \param[in]     search  XPath search result, most typically the result of
  *                        calling <tt>cib->cmds->query()</tt>.
  * \param[in]     name    Name searched for
  */
 void
 pcmk__warn_multiple_name_matches(pcmk__output_t *out, xmlNode *search,
                                  const char *name)
 {
     if (out == NULL || name == NULL || search == NULL ||
         search->children == NULL) {
         return;
     }
 
     out->info(out, "Multiple attributes match " PCMK_XA_NAME "=%s", name);
     pcmk__xe_foreach_child(search, NULL, output_attr_child, out);
 }
 
 // Deprecated functions kept only for backward API compatibility
 // LCOV_EXCL_START
 
 #include <crm/common/xml_compat.h>
 
 xmlXPathObjectPtr
 xpath_search(const xmlNode *xml_top, const char *path)
 {
     CRM_CHECK(xml_top != NULL, return NULL);
 
     return pcmk__xpath_search(xml_top->doc, path);
 }
 
+xmlNode *
+getXpathResult(xmlXPathObjectPtr xpathObj, int index)
+{
+    xmlNode *match = NULL;
+    int max = pcmk__xpath_num_results(xpathObj);
+
+    CRM_CHECK(index >= 0, return NULL);
+    CRM_CHECK(xpathObj != NULL, return NULL);
+
+    if (index >= max) {
+        crm_err("Requested index %d of only %d items", index, max);
+        return NULL;
+
+    } else if(xpathObj->nodesetval->nodeTab[index] == NULL) {
+        /* Previously requested */
+        return NULL;
+    }
+
+    match = xpathObj->nodesetval->nodeTab[index];
+    CRM_CHECK(match != NULL, return NULL);
+
+    if (xpathObj->nodesetval->nodeTab[index]->type != XML_NAMESPACE_DECL) {
+        // See the comment for pcmk__xpath_result()
+        xpathObj->nodesetval->nodeTab[index] = NULL;
+    }
+
+    switch (match->type) {
+        case XML_ELEMENT_NODE:
+            return match;
+
+        case XML_DOCUMENT_NODE: // Searched for '/'
+            return match->children;
+
+        default:
+           if ((match->parent != NULL)
+               && (match->parent->type == XML_ELEMENT_NODE)) {
+                return match->parent;
+           }
+           crm_warn("Unsupported XPath match type %d (bug?)", match->type);
+           return NULL;
+    }
+}
+
 // LCOV_EXCL_STOP
 // End deprecated API