public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Viktor Ashirov <vashirov@redhat.com>
To: git-commits@fedoraproject.org
Subject: [rpms/389-ds-base] f43: Bump version to 3.1.5
Date: Mon, 07 Sep 2026 17:29:54 GMT	[thread overview]
Message-ID: <178880219437.1.7508900179045386620.rpms-389-ds-base-666059dd015b@fedoraproject.org> (raw)

A new commit has been pushed.

Repo   : rpms/389-ds-base
Branch : f43
Commit : 666059dd015bd4bc0c65066c014b700a2bd05e52
Author : Viktor Ashirov <vashirov@redhat.com>
Date   : 2026-09-07T16:11:52+02:00
Stats  : +279/-10115 in 34 file(s)
URL    : https://src.fedoraproject.org/rpms/389-ds-base/c/666059dd015bd4bc0c65066c014b700a2bd05e52?branch=f43

Log:
Bump version to 3.1.5

---
diff --git a/0001-Issue-7150-Compressed-access-log-rotations-skipped-a.patch b/0001-Issue-7150-Compressed-access-log-rotations-skipped-a.patch
deleted file mode 100644
index 7959885..0000000
--- a/0001-Issue-7150-Compressed-access-log-rotations-skipped-a.patch
+++ /dev/null
@@ -1,551 +0,0 @@
-From 045fe1a6899b7e4588be7101e81bb78995a713b1 Mon Sep 17 00:00:00 2001
-From: Simon Pichugin <spichugi@redhat.com>
-Date: Tue, 16 Dec 2025 15:48:35 -0800
-Subject: [PATCH] Issue 7150 - Compressed access log rotations skipped,
- accesslog-list out of sync (#7151)
-
-Description: Accept `.gz`-suffixed rotated log filenames when
-rebuilding rotation info and checking previous logs, preventing
-compressed rotations from being dropped from the internal list.
-
-Add regression tests to stress log rotation with compression,
-verify `nsslapd-accesslog-list` stays in sync, and guard against
-crashes when flushing buffered logs during rotation.
-Minor doc fix in test.
-
-Fixes: https://github.com/389ds/389-ds-base/issues/7150
-
-Reviewed by: @progier389 (Thanks!)
----
- .../suites/logging/log_flush_rotation_test.py | 341 +++++++++++++++++-
- ldap/servers/slapd/log.c                      |  99 +++--
- 2 files changed, 402 insertions(+), 38 deletions(-)
-
-diff --git a/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py b/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py
-index b33a622e1..864ba9c5d 100644
---- a/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py
-+++ b/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py
-@@ -6,6 +6,7 @@
- # See LICENSE for details.
- # --- END COPYRIGHT BLOCK ---
- #
-+import glob
- import os
- import logging
- import time
-@@ -13,14 +14,351 @@ import pytest
- from lib389._constants import DEFAULT_SUFFIX, PW_DM
- from lib389.tasks import ImportTask
- from lib389.idm.user import UserAccounts
-+from lib389.idm.domain import Domain
-+from lib389.idm.directorymanager import DirectoryManager
- from lib389.topologies import topology_st as topo
- 
- 
- log = logging.getLogger(__name__)
- 
- 
-+def remove_rotated_access_logs(inst):
-+    """
-+    Remove all rotated access log files to start fresh for each test.
-+    This prevents log files from previous tests affecting current test results.
-+    """
-+    log_dir = inst.get_log_dir()
-+    patterns = [
-+        f'{log_dir}/access.2*',      # Uncompressed rotated logs
-+        f'{log_dir}/access.*.gz',    # Compressed rotated logs
-+    ]
-+    for pattern in patterns:
-+        for log_file in glob.glob(pattern):
-+            try:
-+                os.remove(log_file)
-+                log.info(f"Removed old log file: {log_file}")
-+            except OSError as e:
-+                log.warning(f"Could not remove {log_file}: {e}")
-+
-+
-+def reset_access_log_config(inst):
-+    """
-+    Reset access log configuration to default values.
-+    """
-+    inst.config.set('nsslapd-accesslog-compress', 'off')
-+    inst.config.set('nsslapd-accesslog-maxlogsize', '100')
-+    inst.config.set('nsslapd-accesslog-maxlogsperdir', '10')
-+    inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off')
-+    inst.config.set('nsslapd-accesslog-logbuffering', 'on')
-+    inst.config.set('nsslapd-accesslog-logexpirationtime', '-1')
-+    inst.config.set('nsslapd-accesslog-logminfreediskspace', '5')
-+
-+
-+def generate_heavy_load(inst, suffix, iterations=50):
-+    """
-+    Generate heavy LDAP load to fill access log quickly.
-+    Performs multiple operations: searches, modifies, binds to populate logs.
-+    """
-+    for i in range(iterations):
-+        suffix.replace('description', f'iteration_{i}')
-+        suffix.get_attr_val('description')
-+
-+
-+def count_access_logs(log_dir, compressed_only=False):
-+    """
-+    Count access log files in the log directory.
-+    Returns count of rotated access logs (not including the active 'access' file).
-+    """
-+    if compressed_only:
-+        pattern = f'{log_dir}/access.*.gz'
-+    else:
-+        pattern = f'{log_dir}/access.2*'
-+    log_files = glob.glob(pattern)
-+    return len(log_files)
-+
-+
-+def test_log_pileup_with_compression(topo):
-+    """Test that log rotation properly deletes old logs when compression is enabled.
-+
-+    :id: fa1bfce8-b6d3-4520-a0a8-bead14fa5838
-+    :setup: Standalone Instance
-+    :steps:
-+        1. Clean up existing rotated logs and reset configuration
-+        2. Enable access log compression
-+        3. Set strict log limits (small maxlogsperdir)
-+        4. Disable log expiration to test count-based deletion
-+        5. Generate heavy load to create many log rotations
-+        6. Verify log count does not exceed maxlogsperdir limit
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. Success
-+        4. Success
-+        5. Success
-+        6. Log count should be at or below maxlogsperdir + small buffer
-+    """
-+
-+    inst = topo.standalone
-+    suffix = Domain(inst, DEFAULT_SUFFIX)
-+    log_dir = inst.get_log_dir()
-+
-+    # Clean up before test
-+    remove_rotated_access_logs(inst)
-+    reset_access_log_config(inst)
-+    inst.restart()
-+
-+    max_logs = 5
-+    inst.config.set('nsslapd-accesslog-compress', 'on')
-+    inst.config.set('nsslapd-accesslog-maxlogsperdir', str(max_logs))
-+    inst.config.set('nsslapd-accesslog-maxlogsize', '1')  # 1MB to trigger rotation
-+    inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off')
-+    inst.config.set('nsslapd-accesslog-logbuffering', 'off')
-+
-+    inst.config.set('nsslapd-accesslog-logexpirationtime', '-1')
-+
-+    inst.config.set('nsslapd-accesslog-logminfreediskspace', '5')
-+
-+    inst.restart()
-+    time.sleep(2)
-+
-+    target_logs = max_logs * 3
-+    for i in range(target_logs):
-+        log.info(f"Generating load for log rotation {i+1}/{target_logs}")
-+        generate_heavy_load(inst, suffix, iterations=150)
-+        time.sleep(1)  # Wait for rotation
-+
-+    time.sleep(3)
-+
-+    logs_on_disk = count_access_logs(log_dir)
-+    log.info(f"Configured maxlogsperdir: {max_logs}")
-+    log.info(f"Actual rotated logs on disk: {logs_on_disk}")
-+
-+    all_access_logs = glob.glob(f'{log_dir}/access*')
-+    log.info(f"All access log files: {all_access_logs}")
-+
-+    max_allowed = max_logs + 2
-+    assert logs_on_disk <= max_allowed, (
-+        f"Log rotation failed to delete old files! "
-+        f"Expected at most {max_allowed} rotated logs (maxlogsperdir={max_logs} + 2 buffer), "
-+        f"but found {logs_on_disk}. The server has lost track of the file list."
-+    )
-+
-+
-+@pytest.mark.parametrize("compress_enabled", ["on", "off"])
-+def test_accesslog_list_mismatch(topo, compress_enabled):
-+    """Test that nsslapd-accesslog-list stays synchronized with actual log files.
-+
-+    :id: 0a8a46a6-cae7-43bd-8b64-5e3481480cd3
-+    :parametrized: yes
-+    :setup: Standalone Instance
-+    :steps:
-+        1. Clean up existing rotated logs and reset configuration
-+        2. Configure log rotation with compression enabled/disabled
-+        3. Generate activity to trigger multiple rotations
-+        4. Get the nsslapd-accesslog-list attribute
-+        5. Compare with actual files on disk
-+        6. Verify they match (accounting for .gz extension when enabled)
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. Success
-+        4. Success
-+        5. Success
-+        6. The list attribute should match actual files on disk
-+    """
-+
-+    inst = topo.standalone
-+    suffix = Domain(inst, DEFAULT_SUFFIX)
-+    log_dir = inst.get_log_dir()
-+    compression_on = compress_enabled == "on"
-+
-+    # Clean up before test
-+    remove_rotated_access_logs(inst)
-+    reset_access_log_config(inst)
-+    inst.restart()
-+
-+    inst.config.set('nsslapd-accesslog-compress', compress_enabled)
-+    inst.config.set('nsslapd-accesslog-maxlogsize', '1')
-+    inst.config.set('nsslapd-accesslog-maxlogsperdir', '10')
-+    inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off')
-+    inst.config.set('nsslapd-accesslog-logbuffering', 'off')
-+    inst.config.set('nsslapd-accesslog-logexpirationtime', '-1')
-+
-+    inst.restart()
-+    time.sleep(2)
-+
-+    for i in range(15):
-+        suffix_note = "(no compression)" if not compression_on else ""
-+        log.info(f"Generating load for rotation {i+1}/15 {suffix_note}")
-+        generate_heavy_load(inst, suffix, iterations=150)
-+        time.sleep(1)
-+
-+    time.sleep(3)
-+
-+    accesslog_list = inst.config.get_attr_vals_utf8('nsslapd-accesslog-list')
-+    log.info(f"nsslapd-accesslog-list entries (compress={compress_enabled}): {len(accesslog_list)}")
-+    log.info(f"nsslapd-accesslog-list (compress={compress_enabled}): {accesslog_list}")
-+
-+    disk_files = glob.glob(f'{log_dir}/access.2*')
-+    log.info(f"Actual files on disk (compress={compress_enabled}): {len(disk_files)}")
-+    log.info(f"Disk files (compress={compress_enabled}): {disk_files}")
-+
-+    disk_files_for_compare = set()
-+    for fpath in disk_files:
-+        if compression_on and fpath.endswith('.gz'):
-+            disk_files_for_compare.add(fpath[:-3])
-+        else:
-+            disk_files_for_compare.add(fpath)
-+
-+    list_files_set = set(accesslog_list)
-+    missing_from_disk = list_files_set - disk_files_for_compare
-+    extra_on_disk = disk_files_for_compare - list_files_set
-+
-+    if missing_from_disk:
-+        log.error(
-+            f"[compress={compress_enabled}] Files in list but NOT on disk: {missing_from_disk}"
-+        )
-+    if extra_on_disk:
-+        log.warning(
-+            f"[compress={compress_enabled}] Files on disk but NOT in list: {extra_on_disk}"
-+        )
-+
-+    assert not missing_from_disk, (
-+        f"nsslapd-accesslog-list mismatch (compress={compress_enabled})! "
-+        f"Files listed but missing from disk: {missing_from_disk}. "
-+        f"This indicates the server's internal list is out of sync with actual files."
-+    )
-+
-+    if len(extra_on_disk) > 2:
-+        log.warning(
-+            f"Potential log tracking issue (compress={compress_enabled}): "
-+            f"{len(extra_on_disk)} files on disk are not tracked in the accesslog-list: "
-+            f"{extra_on_disk}"
-+        )
-+
-+
-+def test_accesslog_list_mixed_compression(topo):
-+    """Test that nsslapd-accesslog-list correctly tracks both compressed and uncompressed logs.
-+
-+    :id: 11b088cd-23be-407d-ad16-4ce2e12da09e
-+    :setup: Standalone Instance
-+    :steps:
-+        1. Clean up existing rotated logs and reset configuration
-+        2. Create rotated logs with compression OFF
-+        3. Enable compression and create more rotated logs
-+        4. Get the nsslapd-accesslog-list attribute
-+        5. Compare with actual files on disk
-+        6. Verify all files are correctly tracked (uncompressed and compressed)
-+    :expectedresults:
-+        1. Success
-+        2. Success - uncompressed rotated logs created
-+        3. Success - compressed rotated logs created
-+        4. Success
-+        5. Success
-+        6. The list should contain base filenames (without .gz) that
-+           correspond to files on disk (either as-is or with .gz suffix)
-+    """
-+
-+    inst = topo.standalone
-+    suffix = Domain(inst, DEFAULT_SUFFIX)
-+    log_dir = inst.get_log_dir()
-+
-+    # Clean up before test
-+    remove_rotated_access_logs(inst)
-+    reset_access_log_config(inst)
-+    inst.restart()
-+
-+    inst.config.set('nsslapd-accesslog-compress', 'off')
-+    inst.config.set('nsslapd-accesslog-maxlogsize', '1')
-+    inst.config.set('nsslapd-accesslog-maxlogsperdir', '20')
-+    inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off')
-+    inst.config.set('nsslapd-accesslog-logbuffering', 'off')
-+    inst.config.set('nsslapd-accesslog-logexpirationtime', '-1')
-+
-+    inst.restart()
-+    time.sleep(2)
-+
-+    for i in range(15):
-+        log.info(f"Generating load for uncompressed rotation {i+1}/15")
-+        generate_heavy_load(inst, suffix, iterations=150)
-+        time.sleep(1)
-+
-+    time.sleep(2)
-+
-+    # Check what we have so far
-+    uncompressed_files = glob.glob(f'{log_dir}/access.2*')
-+    log.info(f"Files on disk after uncompressed phase: {uncompressed_files}")
-+
-+    inst.config.set('nsslapd-accesslog-compress', 'on')
-+    inst.restart()
-+    time.sleep(2)
-+
-+    for i in range(15):
-+        log.info(f"Generating load for compressed rotation {i+1}/15")
-+        generate_heavy_load(inst, suffix, iterations=150)
-+        time.sleep(1)
-+
-+    time.sleep(3)
-+
-+    accesslog_list = inst.config.get_attr_vals_utf8('nsslapd-accesslog-list')
-+
-+    disk_files = glob.glob(f'{log_dir}/access.2*')
-+
-+    log.info(f"nsslapd-accesslog-list entries: {len(accesslog_list)}")
-+    log.info(f"nsslapd-accesslog-list: {sorted(accesslog_list)}")
-+    log.info(f"Actual files on disk: {len(disk_files)}")
-+    log.info(f"Disk files: {sorted(disk_files)}")
-+
-+    compressed_on_disk = [f for f in disk_files if f.endswith('.gz')]
-+    uncompressed_on_disk = [f for f in disk_files if not f.endswith('.gz')]
-+    log.info(f"Compressed files on disk: {compressed_on_disk}")
-+    log.info(f"Uncompressed files on disk: {uncompressed_on_disk}")
-+
-+    list_files_set = set(accesslog_list)
-+
-+    disk_files_base = set()
-+    for fpath in disk_files:
-+        if fpath.endswith('.gz'):
-+            disk_files_base.add(fpath[:-3])  # Strip .gz
-+        else:
-+            disk_files_base.add(fpath)
-+
-+    missing_from_disk = list_files_set - disk_files_base
-+
-+    extra_on_disk = disk_files_base - list_files_set
-+
-+    if missing_from_disk:
-+        log.error(f"Files in list but NOT on disk: {missing_from_disk}")
-+    if extra_on_disk:
-+        log.warning(f"Files on disk but NOT in list: {extra_on_disk}")
-+
-+    assert not missing_from_disk, (
-+        f"nsslapd-accesslog-list contains stale entries! "
-+        f"Files in list but not on disk (as base or .gz): {missing_from_disk}"
-+    )
-+
-+    for list_file in accesslog_list:
-+        exists_uncompressed = os.path.exists(list_file)
-+        exists_compressed = os.path.exists(list_file + '.gz')
-+        assert exists_uncompressed or exists_compressed, (
-+            f"File in accesslog-list does not exist on disk: {list_file} "
-+            f"(checked both {list_file} and {list_file}.gz)"
-+        )
-+        if exists_compressed and not exists_uncompressed:
-+            log.info(f"  {list_file} -> exists as .gz (compressed)")
-+        elif exists_uncompressed:
-+            log.info(f"  {list_file} -> exists (uncompressed)")
-+
-+    if len(extra_on_disk) > 1:
-+        log.warning(
-+            f"Some files on disk are not tracked in accesslog-list: {extra_on_disk}"
-+        )
-+
-+    log.info("Mixed compression test completed successfully")
-+
-+
- def test_log_flush_and_rotation_crash(topo):
--    """Make sure server does not crash whening flushing a buffer and rotating
-+    """Make sure server does not crash when flushing a buffer and rotating
-     the log at the same time
- 
-     :id: d4b0af2f-48b2-45f5-ae8b-f06f692c3133
-@@ -36,6 +374,7 @@ def test_log_flush_and_rotation_crash(topo):
-         3. Success
-         4. Success
-     """
-+    # NOTE: This test is placed last as it may affect the suffix state.
- 
-     inst = topo.standalone
- 
-diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
-index 27bb4bc15..ea744ac1e 100644
---- a/ldap/servers/slapd/log.c
-+++ b/ldap/servers/slapd/log.c
-@@ -137,6 +137,7 @@ static void vslapd_log_emergency_error(LOGFD fp, const char *msg, int locked);
- static int get_syslog_loglevel(int loglevel);
- static void log_external_libs_debug_openldap_print(char *buffer);
- static int log__fix_rotationinfof(char *pathname);
-+static int log__validate_rotated_logname(const char *timestamp_str, PRBool *is_compressed);
- 
- static int
- get_syslog_loglevel(int loglevel)
-@@ -375,7 +376,7 @@ g_log_init()
-     loginfo.log_security_fdes = NULL;
-     loginfo.log_security_file = NULL;
-     loginfo.log_securityinfo_file = NULL;
--    loginfo.log_numof_access_logs = 1;
-+    loginfo.log_numof_security_logs = 1;
-     loginfo.log_security_logchain = NULL;
-     loginfo.log_security_buffer = log_create_buffer(LOG_BUFFER_MAXSIZE);
-     loginfo.log_security_compress = cfg->securitylog_compress;
-@@ -3422,7 +3423,7 @@ log__open_accesslogfile(int logfile_state, int locked)
-                 }
-             } else if (loginfo.log_access_compress) {
-                 if (compress_log_file(newfile, loginfo.log_access_mode) != 0) {
--                    slapi_log_err(SLAPI_LOG_ERR, "log__open_auditfaillogfile",
-+                    slapi_log_err(SLAPI_LOG_ERR, "log__open_accesslogfile",
-                             "failed to compress rotated access log (%s)\n",
-                             newfile);
-                 } else {
-@@ -4825,6 +4826,50 @@ log__delete_rotated_logs()
-     loginfo.log_error_logchain = NULL;
- }
- 
-+/*
-+ * log__validate_rotated_logname
-+ *
-+ * Validates that a log filename timestamp suffix matches the expected format:
-+ * YYYYMMDD-HHMMSS (15 chars) or YYYYMMDD-HHMMSS.gz (18 chars) for compressed files.
-+ * Uses regex pattern: ^[0-9]{8}-[0-9]{6}(\.gz)?$
-+ *
-+ * \param timestamp_str The timestamp portion of the log filename (after the first '.')
-+ * \param is_compressed Output parameter set to PR_TRUE if the file has .gz suffix
-+ * \return 1 if valid, 0 if invalid
-+ */
-+static int
-+log__validate_rotated_logname(const char *timestamp_str, PRBool *is_compressed)
-+{
-+    Slapi_Regex *re = NULL;
-+    char *re_error = NULL;
-+    int rc = 0;
-+
-+    /* Match YYYYMMDD-HHMMSS with optional .gz suffix */
-+    static const char *pattern = "^[0-9]{8}-[0-9]{6}(\\.gz)?$";
-+
-+    *is_compressed = PR_FALSE;
-+
-+    re = slapi_re_comp(pattern, &re_error);
-+    if (re == NULL) {
-+        slapi_log_err(SLAPI_LOG_ERR, "log__validate_rotated_logname",
-+                      "Failed to compile regex: %s\n", re_error ? re_error : "unknown error");
-+        slapi_ch_free_string(&re_error);
-+        return 0;
-+    }
-+
-+    rc = slapi_re_exec_nt(re, timestamp_str);
-+    if (rc == 1) {
-+        /* Check if compressed by looking for .gz suffix */
-+        size_t len = strlen(timestamp_str);
-+        if (len >= 3 && strcmp(timestamp_str + len - 3, ".gz") == 0) {
-+            *is_compressed = PR_TRUE;
-+        }
-+    }
-+
-+    slapi_re_free(re);
-+    return rc == 1 ? 1 : 0;
-+}
-+
- #define ERRORSLOG 1
- #define ACCESSLOG 2
- #define AUDITLOG 3
-@@ -4907,31 +4952,19 @@ log__fix_rotationinfof(char *pathname)
-             }
-         } else if (0 == strncmp(log_type, dirent->name, strlen(log_type)) &&
-                    (p = strchr(dirent->name, '.')) != NULL &&
--                   NULL != strchr(p, '-')) /* e.g., errors.20051123-165135 */
-+                   NULL != strchr(p, '-')) /* e.g., errors.20051123-165135 or errors.20051123-165135.gz */
-         {
-             struct logfileinfo *logp;
--            char *q;
--            int ignoreit = 0;
--
--            for (q = ++p; q && *q; q++) {
--                if (*q != '-' &&
--                    *q != '.' && /* .gz */
--                    *q != 'g' &&
--                    *q != 'z' &&
--                    !isdigit(*q))
--                {
--                    ignoreit = 1;
--                }
--            }
--            if (ignoreit || (q - p != 15)) {
-+            PRBool is_compressed = PR_FALSE;
-+
-+            /* Skip the '.' to get the timestamp portion */
-+            p++;
-+            if (!log__validate_rotated_logname(p, &is_compressed)) {
-                 continue;
-             }
-             logp = (struct logfileinfo *)slapi_ch_malloc(sizeof(struct logfileinfo));
-             logp->l_ctime = log_reverse_convert_time(p);
--            logp->l_compressed = PR_FALSE;
--            if (strcmp(p + strlen(p) - 3, ".gz") == 0) {
--                logp->l_compressed = PR_TRUE;
--            }
-+            logp->l_compressed = is_compressed;
-             PR_snprintf(rotated_log, rotated_log_len, "%s/%s",
-                         logsdir, dirent->name);
- 
-@@ -5098,23 +5131,15 @@ log__check_prevlogs(FILE *fp, char *pathname)
-     for (dirent = PR_ReadDir(dirptr, dirflags); dirent;
-          dirent = PR_ReadDir(dirptr, dirflags)) {
-         if (0 == strncmp(log_type, dirent->name, strlen(log_type)) &&
--            (p = strrchr(dirent->name, '.')) != NULL &&
--            NULL != strchr(p, '-')) { /* e.g., errors.20051123-165135 */
--            char *q;
--            int ignoreit = 0;
--
--            for (q = ++p; q && *q; q++) {
--                if (*q != '-' &&
--                    *q != '.' && /* .gz */
--                    *q != 'g' &&
--                    *q != 'z' &&
--                    !isdigit(*q))
--                {
--                    ignoreit = 1;
--                }
--            }
--            if (ignoreit || (q - p != 15))
-+            (p = strchr(dirent->name, '.')) != NULL &&
-+            NULL != strchr(p, '-')) { /* e.g., errors.20051123-165135 or errors.20051123-165135.gz */
-+            PRBool is_compressed = PR_FALSE;
-+
-+            /* Skip the '.' to get the timestamp portion */
-+            p++;
-+            if (!log__validate_rotated_logname(p, &is_compressed)) {
-                 continue;
-+            }
- 
-             fseek(fp, 0, SEEK_SET);
-             buf[BUFSIZ - 1] = '\0';
--- 
-2.52.0
-

diff --git a/0002-Sync-lib389-version-to-3.1.4-7161.patch b/0002-Sync-lib389-version-to-3.1.4-7161.patch
deleted file mode 100644
index 08d7c94..0000000
--- a/0002-Sync-lib389-version-to-3.1.4-7161.patch
+++ /dev/null
@@ -1,37 +0,0 @@
-From 3b133a5ed6fa89939a569fe6130b325726f4e50c Mon Sep 17 00:00:00 2001
-From: Stanislav Levin <slev@altlinux.org>
-Date: Fri, 19 Dec 2025 14:52:48 +0300
-Subject: [PATCH] Sync lib389 version to 3.1.4 (#7161)
-
-Prepared with:
-$ python3 validate_version.py --update
-ERROR: Version mismatch detected!
-Main project version: 3.1.4
-lib389 version:       3.1.3
-SUCCESS: Updated lib389 version to 3.1.4 in pyproject.toml
-
-Fixes: https://github.com/389ds/389-ds-base/issues/7160
-
-Reviewed by:  @progier (Thanks!)
-
-Signed-off-by: Stanislav Levin <slev@altlinux.org>
----
- src/lib389/pyproject.toml | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/src/lib389/pyproject.toml b/src/lib389/pyproject.toml
-index 1cd840713..85c0c5141 100644
---- a/src/lib389/pyproject.toml
-+++ b/src/lib389/pyproject.toml
-@@ -16,7 +16,7 @@ build-backend = "setuptools.build_meta"
- 
- [project]
- name = "lib389"
--version = "3.1.3"  # Should match the main 389-ds-base version
-+version = "3.1.4"  # Should match the main 389-ds-base version
- description = "A library for accessing, testing, and configuring the 389 Directory Server"
- readme = "README.md"
- license = {text = "GPL-3.0-or-later"}
--- 
-2.52.0
-

diff --git a/0003-Issue-7166-db_config_set-asserts-because-of-dynamic-.patch b/0003-Issue-7166-db_config_set-asserts-because-of-dynamic-.patch
deleted file mode 100644
index cf571ba..0000000
--- a/0003-Issue-7166-db_config_set-asserts-because-of-dynamic-.patch
+++ /dev/null
@@ -1,33 +0,0 @@
-From 9adaeba848a5b0fbe5d3a6148736f6c2ae940c35 Mon Sep 17 00:00:00 2001
-From: progier389 <progier@redhat.com>
-Date: Mon, 5 Jan 2026 14:38:38 +0100
-Subject: [PATCH] Issue 7166 - db_config_set asserts because of dynamic list
- (#7167)
-
-Avoid assertion in db_config_set when args does not contains dynamic list attributes
-
-Issue: #7166
-
-Reviewed by: @tbordaz (Thanks!)
-
-(cherry picked from commit 5f15223280002803a932187c22b10beaeaa74bc2)
----
- src/lib389/lib389/cli_conf/backend.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
-index 677b37fcb..d0ec4bd9e 100644
---- a/src/lib389/lib389/cli_conf/backend.py
-+++ b/src/lib389/lib389/cli_conf/backend.py
-@@ -544,7 +544,7 @@ def db_config_set(inst, basedn, log, args):
-     did_something = False
-     replace_list = []
- 
--    if args.enable_dynamic_lists and args.disable_dynamic_lists:
-+    if getattr(args,'enable_dynamic_lists', None) and getattr(args, 'disable_dynamic_lists', None):
-         raise ValueError("You can not enable and disable dynamic lists at the same time")
- 
-     for attr, value in list(attrs.items()):
--- 
-2.52.0
-

diff --git a/0004-Issue-7160-Add-lib389-version-sync-check-to-configur.patch b/0004-Issue-7160-Add-lib389-version-sync-check-to-configur.patch
deleted file mode 100644
index 2367d6a..0000000
--- a/0004-Issue-7160-Add-lib389-version-sync-check-to-configur.patch
+++ /dev/null
@@ -1,57 +0,0 @@
-From 7693d5335c498de1dbb783042cc4acec0138e44d Mon Sep 17 00:00:00 2001
-From: Simon Pichugin <spichugi@redhat.com>
-Date: Mon, 5 Jan 2026 18:32:52 -0800
-Subject: [PATCH] Issue 7160 - Add lib389 version sync check to configure
- (#7165)
-
-Description: Add version validation during configure that ensures
-lib389 version in pyproject.toml matches the main project version
-in VERSION.sh. Configure fails with clear error message and fix
-instructions when versions mismatch, preventing inconsistent releases.
-
-Fixes: https://github.com/389ds/389-ds-base/issues/7160
-
-Reviewed by: @progier389 (Thanks!)
----
- configure.ac | 25 +++++++++++++++++++++++++
- 1 file changed, 25 insertions(+)
-
-diff --git a/configure.ac b/configure.ac
-index e94f72647..7fd061a8c 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -7,6 +7,31 @@ AC_CONFIG_HEADERS([config.h])
- # include the version information
- . $srcdir/VERSION.sh
- AC_MSG_NOTICE(This is configure for $PACKAGE_TARNAME $PACKAGE_VERSION)
-+
-+# Validate lib389 version matches main project version
-+AC_MSG_CHECKING([lib389 version sync])
-+lib389_pyproject="$srcdir/src/lib389/pyproject.toml"
-+if test -f "$lib389_pyproject"; then
-+    lib389_version=$(grep -E '^version\s*=' "$lib389_pyproject" | sed 's/.*"\(.*\)".*/\1/')
-+    if test "x$lib389_version" != "x$RPM_VERSION"; then
-+        AC_MSG_RESULT([MISMATCH])
-+        AC_MSG_ERROR([
-+lib389 version mismatch detected!
-+  Main project version (VERSION.sh): $RPM_VERSION
-+  lib389 version (pyproject.toml):   $lib389_version
-+
-+To fix this, run:
-+  cd $srcdir/src/lib389 && python3 validate_version.py --update
-+
-+lib389 version MUST match the main project version before release.
-+])
-+    else
-+        AC_MSG_RESULT([ok ($lib389_version)])
-+    fi
-+else
-+    AC_MSG_RESULT([MISSING])
-+    AC_MSG_ERROR([lib389 pyproject.toml not found at $lib389_pyproject - source tree is incomplete])
-+fi
- AM_INIT_AUTOMAKE([1.9 foreign subdir-objects dist-bzip2 no-dist-gzip no-define tar-pax])
- AC_SUBST([RPM_VERSION])
- AC_SUBST([RPM_RELEASE])
--- 
-2.52.0
-

diff --git a/0005-Issue-7096-During-replication-online-total-init-the-.patch b/0005-Issue-7096-During-replication-online-total-init-the-.patch
deleted file mode 100644
index fddca98..0000000
--- a/0005-Issue-7096-During-replication-online-total-init-the-.patch
+++ /dev/null
@@ -1,318 +0,0 @@
-From 5de98cdc10bd333d0695c00d57e137d699f90f1c Mon Sep 17 00:00:00 2001
-From: tbordaz <tbordaz@redhat.com>
-Date: Wed, 7 Jan 2026 11:21:12 +0100
-Subject: [PATCH] Issue 7096 - During replication online total init the
- function idl_id_is_in_idlist is not scaling with large database (#7145)
-
-Bug description:
-	During a online total initialization, the supplier sorts
-	the candidate list of entries so that the parents are sent before
-	children entries.
-	With large DB the ID array used for the sorting is not
-	scaling. It takes so long to build the candidate list that
-	the connection gets closed
-
-Fix description:
-        Instead of using an ID array, uses a list of ID ranges
-
-fixes: #7096
-
-Reviewed by: Mark Reynolds, Pierre Rogier (Thanks !!)
----
- ldap/servers/slapd/back-ldbm/back-ldbm.h      |  12 ++
- ldap/servers/slapd/back-ldbm/idl_common.c     | 163 ++++++++++++++++++
- ldap/servers/slapd/back-ldbm/idl_new.c        |  30 ++--
- .../servers/slapd/back-ldbm/proto-back-ldbm.h |   3 +
- 4 files changed, 189 insertions(+), 19 deletions(-)
-
-diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
-index 1bc36720d..b187c26bc 100644
---- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
-+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
-@@ -282,6 +282,18 @@ typedef struct _idlist_set
- #define INDIRECT_BLOCK(idl) ((idl)->b_nids == INDBLOCK)
- #define IDL_NIDS(idl)       (idl ? (idl)->b_nids : (NIDS)0)
- 
-+/*
-+ * used by the supplier during online total init
-+ * it stores the ranges of ID that are already present
-+ * in the candidate list ('parentid>=1')
-+ */
-+typedef struct IdRange {
-+    ID first;
-+    ID last;
-+    struct IdRange *next;
-+} IdRange_t;
-+
-+
- typedef size_t idl_iterator;
- 
- /* small hashtable implementation used in the entry cache -- the table
-diff --git a/ldap/servers/slapd/back-ldbm/idl_common.c b/ldap/servers/slapd/back-ldbm/idl_common.c
-index fcb0ece4b..fdc9b4e67 100644
---- a/ldap/servers/slapd/back-ldbm/idl_common.c
-+++ b/ldap/servers/slapd/back-ldbm/idl_common.c
-@@ -172,6 +172,169 @@ idl_min(IDList *a, IDList *b)
-     return (a->b_nids > b->b_nids ? b : a);
- }
- 
-+/*
-+ * This is a faster version of idl_id_is_in_idlist.
-+ * idl_id_is_in_idlist uses an array of ID so lookup is expensive
-+ * idl_id_is_in_idlist_ranges uses a list of ranges of ID lookup is faster
-+ * returns
-+ *   1: 'id' is present in idrange_list
-+ *   0: 'id' is not present in idrange_list
-+ */
-+int
-+idl_id_is_in_idlist_ranges(IDList *idl, IdRange_t *idrange_list, ID id)
-+{
-+    IdRange_t *range = idrange_list;
-+    int found = 0;
-+
-+    if (NULL == idl || NOID == id) {
-+        return 0; /* not in the list */
-+    }
-+    if (ALLIDS(idl)) {
-+        return 1; /* in the list */
-+    }
-+
-+    for(;range; range = range->next) {
-+        if (id > range->last) {
-+            /* check if it belongs to the next range */
-+            continue;
-+        }
-+        if (id >= range->first) {
-+            /* It belongs to that range [first..last ] */
-+            found = 1;
-+            break;
-+        } else {
-+            /* this range is after id */
-+            break;
-+        }
-+    }
-+    return found;
-+}
-+
-+/* This function is used during the online total initialisation
-+ * (see next function)
-+ * It frees all ranges of ID in the list
-+ */
-+void idrange_free(IdRange_t **head)
-+{
-+    IdRange_t *curr, *sav;
-+
-+    if ((head == NULL) || (*head == NULL)) {
-+        return;
-+    }
-+    curr = *head;
-+    sav = NULL;
-+    for (; curr;) {
-+        sav = curr;
-+        curr = curr->next;
-+        slapi_ch_free((void *) &sav);
-+    }
-+    if (sav) {
-+        slapi_ch_free((void *) &sav);
-+    }
-+    *head = NULL;
-+}
-+
-+/* This function is used during the online total initialisation
-+ * Because a MODRDN can move entries under a parent that
-+ * has a higher ID we need to sort the IDList so that parents
-+ * are sent, to the consumer, before the children are sent.
-+ * The sorting with a simple IDlist does not scale instead
-+ * a list of IDs ranges is much faster.
-+ * In that list we only ADD/lookup ID.
-+ */
-+IdRange_t *idrange_add_id(IdRange_t **head, ID id)
-+{
-+    if (head == NULL) {
-+        slapi_log_err(SLAPI_LOG_ERR, "idrange_add_id",
-+                      "Can not add ID %d in non defined list\n", id);
-+        return NULL;
-+    }
-+
-+    if (*head == NULL) {
-+        /* This is the first range */
-+        IdRange_t *new_range = (IdRange_t *)slapi_ch_malloc(sizeof(IdRange_t));
-+        new_range->first = id;
-+        new_range->last = id;
-+        new_range->next = NULL;
-+        *head = new_range;
-+        return *head;
-+    }
-+
-+    IdRange_t *curr = *head, *prev = NULL;
-+
-+    /* First, find if id already falls within any existing range, or it is adjacent to any */
-+    while (curr) {
-+        if (id >= curr->first && id <= curr->last) {
-+            /* inside a range, nothing to do */
-+            return curr;
-+        }
-+
-+        if (id == curr->last + 1) {
-+            /* Extend this range upwards */
-+            curr->last = id;
-+
-+            /* Check for possible merge with next range */
-+            IdRange_t *next = curr->next;
-+            if (next && curr->last + 1 >= next->first) {
-+                slapi_log_err(SLAPI_LOG_REPL, "idrange_add_id",
-+                        "(id=%d) merge current  with next range [%d..%d]\n", id, curr->first, curr->last);
-+                curr->last = (next->last > curr->last) ? next->last : curr->last;
-+                curr->next = next->next;
-+                slapi_ch_free((void*) &next);
-+            } else {
-+                slapi_log_err(SLAPI_LOG_REPL, "idrange_add_id",
-+                              "(id=%d) extend forward current range [%d..%d]\n", id, curr->first, curr->last);
-+            }
-+            return curr;
-+        }
-+
-+        if (id + 1 == curr->first) {
-+            /* Extend this range downwards */
-+            curr->first = id;
-+
-+            /* Check for possible merge with previous range */
-+            if (prev && prev->last + 1 >= curr->first) {
-+                prev->last = curr->last;
-+                prev->next = curr->next;
-+                slapi_ch_free((void *) &curr);
-+                slapi_log_err(SLAPI_LOG_REPL, "idrange_add_id",
-+                              "(id=%d) merge current with previous range [%d..%d]\n", id, prev->first, prev->last);
-+                return prev;
-+            } else {
-+                slapi_log_err(SLAPI_LOG_REPL, "idrange_add_id",
-+                              "(id=%d) extend backward current range [%d..%d]\n", id, curr->first, curr->last);
-+                return curr;
-+            }
-+        }
-+
-+        /* If id is before the current range, break so we can insert before */
-+        if (id < curr->first) {
-+            break;
-+        }
-+
-+        prev = curr;
-+        curr = curr->next;
-+    }
-+    /* Need to insert a new standalone IdRange */
-+    IdRange_t *new_range = (IdRange_t *)slapi_ch_malloc(sizeof(IdRange_t));
-+    new_range->first = id;
-+    new_range->last = id;
-+    new_range->next = curr;
-+
-+    if (prev) {
-+        slapi_log_err(SLAPI_LOG_REPL, "idrange_add_id",
-+                      "(id=%d) add new range [%d..%d]\n", id, new_range->first, new_range->last);
-+        prev->next = new_range;
-+    } else {
-+        /* Insert at head */
-+        slapi_log_err(SLAPI_LOG_REPL, "idrange_add_id",
-+                      "(id=%d) head range [%d..%d]\n", id, new_range->first, new_range->last);
-+        *head = new_range;
-+    }
-+    return *head;
-+}
-+
-+
- int
- idl_id_is_in_idlist(IDList *idl, ID id)
- {
-diff --git a/ldap/servers/slapd/back-ldbm/idl_new.c b/ldap/servers/slapd/back-ldbm/idl_new.c
-index 5fbcaff2e..2d978353f 100644
---- a/ldap/servers/slapd/back-ldbm/idl_new.c
-+++ b/ldap/servers/slapd/back-ldbm/idl_new.c
-@@ -417,7 +417,6 @@ idl_new_range_fetch(
- {
-     int ret = 0;
-     int ret2 = 0;
--    int idl_rc = 0;
-     dbi_cursor_t cursor = {0};
-     IDList *idl = NULL;
-     dbi_val_t cur_key = {0};
-@@ -436,6 +435,7 @@ idl_new_range_fetch(
-     size_t leftoverlen = 32;
-     size_t leftovercnt = 0;
-     char *index_id = get_index_name(be, db, ai);
-+    IdRange_t *idrange_list = NULL;
- 
- 
-     if (NULL == flag_err) {
-@@ -578,10 +578,12 @@ idl_new_range_fetch(
-                      * found entry is the one from the suffix
-                      */
-                     suffix = key;
--                    idl_rc = idl_append_extend(&idl, id);
--                } else if ((key == suffix) || idl_id_is_in_idlist(idl, key)) {
-+                    idl_append_extend(&idl, id);
-+                    idrange_add_id(&idrange_list, id);
-+                } else if ((key == suffix) || idl_id_is_in_idlist_ranges(idl, idrange_list, key)) {
-                     /* the parent is the suffix or already in idl. */
--                    idl_rc = idl_append_extend(&idl, id);
-+                    idl_append_extend(&idl, id);
-+                    idrange_add_id(&idrange_list, id);
-                 } else {
-                     /* Otherwise, keep the {key,id} in leftover array */
-                     if (!leftover) {
-@@ -596,13 +598,7 @@ idl_new_range_fetch(
-                     leftovercnt++;
-                 }
-             } else {
--                idl_rc = idl_append_extend(&idl, id);
--            }
--            if (idl_rc) {
--                slapi_log_err(SLAPI_LOG_ERR, "idl_new_range_fetch",
--                              "Unable to extend id list (err=%d)\n", idl_rc);
--                idl_free(&idl);
--                goto error;
-+                idl_append_extend(&idl, id);
-             }
- 
-             count++;
-@@ -695,21 +691,17 @@ error:
- 
-         while(remaining > 0) {
-             for (size_t i = 0; i < leftovercnt; i++) {
--                if (leftover[i].key > 0 && idl_id_is_in_idlist(idl, leftover[i].key) != 0) {
-+                if (leftover[i].key > 0 && idl_id_is_in_idlist_ranges(idl, idrange_list, leftover[i].key) != 0) {
-                     /* if the leftover key has its parent in the idl */
--                    idl_rc = idl_append_extend(&idl, leftover[i].id);
--                    if (idl_rc) {
--                        slapi_log_err(SLAPI_LOG_ERR, "idl_new_range_fetch",
--                                      "Unable to extend id list (err=%d)\n", idl_rc);
--                        idl_free(&idl);
--                        return NULL;
--                    }
-+                    idl_append_extend(&idl, leftover[i].id);
-+                    idrange_add_id(&idrange_list, leftover[i].id);
-                     leftover[i].key = 0;
-                     remaining--;
-                 }
-             }
-         }
-         slapi_ch_free((void **)&leftover);
-+        idrange_free(&idrange_list);
-     }
-     slapi_log_err(SLAPI_LOG_FILTER, "idl_new_range_fetch",
-                   "Found %d candidates; error code is: %d\n",
-diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
-index 91d61098a..30a7aa11f 100644
---- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
-+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
-@@ -217,6 +217,9 @@ ID idl_firstid(IDList *idl);
- ID idl_nextid(IDList *idl, ID id);
- int idl_init_private(backend *be, struct attrinfo *a);
- int idl_release_private(struct attrinfo *a);
-+IdRange_t *idrange_add_id(IdRange_t **head, ID id);
-+void idrange_free(IdRange_t **head);
-+int idl_id_is_in_idlist_ranges(IDList *idl, IdRange_t *idrange_list, ID id);
- int idl_id_is_in_idlist(IDList *idl, ID id);
- 
- idl_iterator idl_iterator_init(const IDList *idl);
--- 
-2.52.0
-

diff --git a/0006-Issue-Revise-paged-result-search-locking.patch b/0006-Issue-Revise-paged-result-search-locking.patch
deleted file mode 100644
index 10e7dd5..0000000
--- a/0006-Issue-Revise-paged-result-search-locking.patch
+++ /dev/null
@@ -1,765 +0,0 @@
-From 6f3bf5a48d504646751be9e91293487eec972ed8 Mon Sep 17 00:00:00 2001
-From: Mark Reynolds <mreynolds@redhat.com>
-Date: Wed, 7 Jan 2026 16:55:27 -0500
-Subject: [PATCH] Issue - Revise paged result search locking
-
-Description:
-
-Move to a single lock approach verses having two locks. This will impact
-concurrency when multiple async paged result searches are done on the same
-connection, but it simplifies the code and avoids race conditions and
-deadlocks.
-
-Relates: https://github.com/389ds/389-ds-base/issues/7118
-
-Reviewed by: progier & tbordaz (Thanks!!)
----
- ldap/servers/slapd/abandon.c      |   2 +-
- ldap/servers/slapd/opshared.c     |  60 ++++----
- ldap/servers/slapd/pagedresults.c | 228 +++++++++++++++++++-----------
- ldap/servers/slapd/proto-slap.h   |  26 ++--
- ldap/servers/slapd/slap.h         |   5 +-
- 5 files changed, 187 insertions(+), 134 deletions(-)
-
-diff --git a/ldap/servers/slapd/abandon.c b/ldap/servers/slapd/abandon.c
-index 6024fcd31..1f47c531c 100644
---- a/ldap/servers/slapd/abandon.c
-+++ b/ldap/servers/slapd/abandon.c
-@@ -179,7 +179,7 @@ do_abandon(Slapi_PBlock *pb)
-     logpb.tv_sec = -1;
-     logpb.tv_nsec = -1;
- 
--    if (0 == pagedresults_free_one_msgid(pb_conn, id, pageresult_lock_get_addr(pb_conn))) {
-+    if (0 == pagedresults_free_one_msgid(pb_conn, id, PR_NOT_LOCKED)) {
-         if (log_format != LOG_FORMAT_DEFAULT) {
-             /* JSON logging */
-             logpb.target_op = "Simple Paged Results";
-diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c
-index a5cddfd23..bf800f7dc 100644
---- a/ldap/servers/slapd/opshared.c
-+++ b/ldap/servers/slapd/opshared.c
-@@ -572,8 +572,8 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
-                         be = be_list[index];
-                     }
-                 }
--                pr_search_result = pagedresults_get_search_result(pb_conn, operation, 0 /*not locked*/, pr_idx);
--                estimate = pagedresults_get_search_result_set_size_estimate(pb_conn, operation, pr_idx);
-+                pr_search_result = pagedresults_get_search_result(pb_conn, operation, PR_NOT_LOCKED, pr_idx);
-+                estimate = pagedresults_get_search_result_set_size_estimate(pb_conn, operation, PR_NOT_LOCKED, pr_idx);
-                 /* Set operation note flags as required. */
-                 if (pagedresults_get_unindexed(pb_conn, operation, pr_idx)) {
-                     slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_UNINDEXED);
-@@ -619,14 +619,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
-             int32_t tlimit;
-             slapi_pblock_get(pb, SLAPI_SEARCH_TIMELIMIT, &tlimit);
-             pagedresults_set_timelimit(pb_conn, operation, (time_t)tlimit, pr_idx);
--            /* When using this mutex in conjunction with the main paged
--             * result lock, you must do so in this order:
--             *
--             * --> pagedresults_lock()
--             *    --> pagedresults_mutex
--             *    <-- pagedresults_mutex
--             * <-- pagedresults_unlock()
--             */
-+            /* IMPORTANT: Never acquire pagedresults_mutex when holding c_mutex. */
-             pagedresults_mutex = pageresult_lock_get_addr(pb_conn);
-         }
- 
-@@ -743,17 +736,15 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
-         if (op_is_pagedresults(operation) && pr_search_result) {
-             void *sr = NULL;
-             /* PAGED RESULTS and already have the search results from the prev op */
--            pagedresults_lock(pb_conn, pr_idx);
-             /*
-              * In async paged result case, the search result might be released
-              * by other theads.  We need to double check it in the locked region.
-              */
-             pthread_mutex_lock(pagedresults_mutex);
--            pr_search_result = pagedresults_get_search_result(pb_conn, operation, 1 /*locked*/, pr_idx);
-+            pr_search_result = pagedresults_get_search_result(pb_conn, operation, PR_LOCKED, pr_idx);
-             if (pr_search_result) {
--                if (pagedresults_is_abandoned_or_notavailable(pb_conn, 1 /*locked*/, pr_idx)) {
-+                if (pagedresults_is_abandoned_or_notavailable(pb_conn, PR_LOCKED, pr_idx)) {
-                     pthread_mutex_unlock(pagedresults_mutex);
--                    pagedresults_unlock(pb_conn, pr_idx);
-                     /* Previous operation was abandoned and the simplepaged object is not in use. */
-                     send_ldap_result(pb, 0, NULL, "Simple Paged Results Search abandoned", 0, NULL);
-                     rc = LDAP_SUCCESS;
-@@ -764,14 +755,13 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
- 
-                     /* search result could be reset in the backend/dse */
-                     slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_SET, &sr);
--                    pagedresults_set_search_result(pb_conn, operation, sr, 1 /*locked*/, pr_idx);
-+                    pagedresults_set_search_result(pb_conn, operation, sr, PR_LOCKED, pr_idx);
-                 }
-             } else {
-                 pr_stat = PAGEDRESULTS_SEARCH_END;
-                 rc = LDAP_SUCCESS;
-             }
-             pthread_mutex_unlock(pagedresults_mutex);
--            pagedresults_unlock(pb_conn, pr_idx);
- 
-             if ((PAGEDRESULTS_SEARCH_END == pr_stat) || (0 == pnentries)) {
-                 /* no more entries to send in the backend */
-@@ -789,22 +779,22 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
-             }
-             pagedresults_set_response_control(pb, 0, estimate,
-                                               curr_search_count, pr_idx);
--            if (pagedresults_get_with_sort(pb_conn, operation, pr_idx)) {
-+            if (pagedresults_get_with_sort(pb_conn, operation, PR_NOT_LOCKED, pr_idx)) {
-                 sort_make_sort_response_control(pb, CONN_GET_SORT_RESULT_CODE, NULL);
-             }
-             pagedresults_set_search_result_set_size_estimate(pb_conn,
-                                                              operation,
--                                                             estimate, pr_idx);
-+                                                             estimate, PR_NOT_LOCKED, pr_idx);
-             if (PAGEDRESULTS_SEARCH_END == pr_stat) {
--                pagedresults_lock(pb_conn, pr_idx);
-+                pthread_mutex_lock(pagedresults_mutex);
-                 slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET, NULL);
--                if (!pagedresults_is_abandoned_or_notavailable(pb_conn, 0 /*not locked*/, pr_idx)) {
--                    pagedresults_free_one(pb_conn, operation, pr_idx);
-+                if (!pagedresults_is_abandoned_or_notavailable(pb_conn, PR_LOCKED, pr_idx)) {
-+                    pagedresults_free_one(pb_conn, operation, PR_LOCKED, pr_idx);
-                 }
--                pagedresults_unlock(pb_conn, pr_idx);
-+                pthread_mutex_unlock(pagedresults_mutex);
-                 if (next_be) {
-                     /* no more entries, but at least another backend */
--                    if (pagedresults_set_current_be(pb_conn, next_be, pr_idx, 0) < 0) {
-+                    if (pagedresults_set_current_be(pb_conn, next_be, pr_idx, PR_NOT_LOCKED) < 0) {
-                         goto free_and_return;
-                     }
-                 }
-@@ -915,7 +905,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
-                         }
-                     }
-                     pagedresults_set_search_result(pb_conn, operation, NULL, 1, pr_idx);
--                    rc = pagedresults_set_current_be(pb_conn, NULL, pr_idx, 1);
-+                    rc = pagedresults_set_current_be(pb_conn, NULL, pr_idx, PR_LOCKED);
-                     pthread_mutex_unlock(pagedresults_mutex);
- #pragma GCC diagnostic pop
-                 }
-@@ -954,7 +944,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
-                         pthread_mutex_lock(pagedresults_mutex);
-                         pagedresults_set_search_result(pb_conn, operation, NULL, 1, pr_idx);
-                         be->be_search_results_release(&sr);
--                        rc = pagedresults_set_current_be(pb_conn, next_be, pr_idx, 1);
-+                        rc = pagedresults_set_current_be(pb_conn, next_be, pr_idx, PR_LOCKED);
-                         pthread_mutex_unlock(pagedresults_mutex);
-                         pr_stat = PAGEDRESULTS_SEARCH_END; /* make sure stat is SEARCH_END */
-                         if (NULL == next_be) {
-@@ -967,23 +957,23 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
-                     } else {
-                         curr_search_count = pnentries;
-                         slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_SET_SIZE_ESTIMATE, &estimate);
--                        pagedresults_lock(pb_conn, pr_idx);
--                        if ((pagedresults_set_current_be(pb_conn, be, pr_idx, 0) < 0) ||
--                            (pagedresults_set_search_result(pb_conn, operation, sr, 0, pr_idx) < 0) ||
--                            (pagedresults_set_search_result_count(pb_conn, operation, curr_search_count, pr_idx) < 0) ||
--                            (pagedresults_set_search_result_set_size_estimate(pb_conn, operation, estimate, pr_idx) < 0) ||
--                            (pagedresults_set_with_sort(pb_conn, operation, with_sort, pr_idx) < 0)) {
--                            pagedresults_unlock(pb_conn, pr_idx);
-+                        pthread_mutex_lock(pagedresults_mutex);
-+                        if ((pagedresults_set_current_be(pb_conn, be, pr_idx, PR_LOCKED) < 0) ||
-+                            (pagedresults_set_search_result(pb_conn, operation, sr, PR_LOCKED, pr_idx) < 0) ||
-+                            (pagedresults_set_search_result_count(pb_conn, operation, curr_search_count, PR_LOCKED, pr_idx) < 0) ||
-+                            (pagedresults_set_search_result_set_size_estimate(pb_conn, operation, estimate, PR_LOCKED, pr_idx) < 0) ||
-+                            (pagedresults_set_with_sort(pb_conn, operation, with_sort, PR_LOCKED, pr_idx) < 0)) {
-+                            pthread_mutex_unlock(pagedresults_mutex);
-                             cache_return_target_entry(pb, be, operation);
-                             goto free_and_return;
-                         }
--                        pagedresults_unlock(pb_conn, pr_idx);
-+                        pthread_mutex_unlock(pagedresults_mutex);
-                     }
-                     slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET, NULL);
-                     next_be = NULL; /* to break the loop */
-                     if (operation->o_status & SLAPI_OP_STATUS_ABANDONED) {
-                         /* It turned out this search was abandoned. */
--                        pagedresults_free_one_msgid(pb_conn, operation->o_msgid, pagedresults_mutex);
-+                        pagedresults_free_one_msgid(pb_conn, operation->o_msgid, PR_NOT_LOCKED);
-                         /* paged-results-request was abandoned; making an empty cookie. */
-                         pagedresults_set_response_control(pb, 0, estimate, -1, pr_idx);
-                         send_ldap_result(pb, 0, NULL, "Simple Paged Results Search abandoned", 0, NULL);
-@@ -993,7 +983,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
-                     }
-                     pagedresults_set_response_control(pb, 0, estimate, curr_search_count, pr_idx);
-                     if (curr_search_count == -1) {
--                        pagedresults_free_one(pb_conn, operation, pr_idx);
-+                        pagedresults_free_one(pb_conn, operation, PR_NOT_LOCKED, pr_idx);
-                     }
-                 }
- 
-diff --git a/ldap/servers/slapd/pagedresults.c b/ldap/servers/slapd/pagedresults.c
-index 941ab97e3..0d6c4a1aa 100644
---- a/ldap/servers/slapd/pagedresults.c
-+++ b/ldap/servers/slapd/pagedresults.c
-@@ -34,9 +34,9 @@ pageresult_lock_cleanup()
-     slapi_ch_free((void**)&lock_hash);
- }
- 
--/* Beware to the lock order with c_mutex:
-- * c_mutex is sometime locked while holding pageresult_lock
-- * ==> Do not lock pageresult_lock when holing c_mutex
-+/* Lock ordering constraint with c_mutex:
-+ * c_mutex is sometimes locked while holding pageresult_lock.
-+ * Therefore: DO NOT acquire pageresult_lock when holding c_mutex.
-  */
- pthread_mutex_t *
- pageresult_lock_get_addr(Connection *conn)
-@@ -44,7 +44,11 @@ pageresult_lock_get_addr(Connection *conn)
-     return &lock_hash[(((size_t)conn)/sizeof (Connection))%LOCK_HASH_SIZE];
- }
- 
--/* helper function to clean up one prp slot */
-+/* helper function to clean up one prp slot
-+ *
-+ * NOTE: This function must be called while holding the pageresult_lock
-+ * (via pageresult_lock_get_addr(conn)) to ensure thread-safe cleanup.
-+ */
- static void
- _pr_cleanup_one_slot(PagedResults *prp)
- {
-@@ -56,7 +60,7 @@ _pr_cleanup_one_slot(PagedResults *prp)
-         prp->pr_current_be->be_search_results_release(&(prp->pr_search_result_set));
-     }
- 
--    /* clean up the slot except the mutex */
-+    /* clean up the slot */
-     prp->pr_current_be = NULL;
-     prp->pr_search_result_set = NULL;
-     prp->pr_search_result_count = 0;
-@@ -136,6 +140,8 @@ pagedresults_parse_control_value(Slapi_PBlock *pb,
-         return LDAP_UNWILLING_TO_PERFORM;
-     }
- 
-+    /* Acquire hash-based lock for paged results list access
-+     * IMPORTANT: Never acquire this lock when holding c_mutex */
-     pthread_mutex_lock(pageresult_lock_get_addr(conn));
-     /* the ber encoding is no longer needed */
-     ber_free(ber, 1);
-@@ -184,10 +190,6 @@ pagedresults_parse_control_value(Slapi_PBlock *pb,
-             goto bail;
-         }
- 
--        if ((*index > -1) && (*index < conn->c_pagedresults.prl_maxlen) &&
--            !conn->c_pagedresults.prl_list[*index].pr_mutex) {
--            conn->c_pagedresults.prl_list[*index].pr_mutex = PR_NewLock();
--        }
-         conn->c_pagedresults.prl_count++;
-     } else {
-         /* Repeated paged results request.
-@@ -327,8 +329,14 @@ bailout:
-                   "<= idx=%d\n", index);
- }
- 
-+/*
-+ * Free one paged result entry by index.
-+ *
-+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
-+ * caller already holds pageresult_lock. Never call when holding c_mutex.
-+ */
- int
--pagedresults_free_one(Connection *conn, Operation *op, int index)
-+pagedresults_free_one(Connection *conn, Operation *op, bool locked, int index)
- {
-     int rc = -1;
- 
-@@ -338,7 +346,9 @@ pagedresults_free_one(Connection *conn, Operation *op, int index)
-     slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one",
-                   "=> idx=%d\n", index);
-     if (conn && (index > -1)) {
--        pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        }
-         if (conn->c_pagedresults.prl_count <= 0) {
-             slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one",
-                           "conn=%" PRIu64 " paged requests list count is %d\n",
-@@ -349,7 +359,9 @@ pagedresults_free_one(Connection *conn, Operation *op, int index)
-             conn->c_pagedresults.prl_count--;
-             rc = 0;
-         }
--        pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        }
-     }
- 
-     slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one", "<= %d\n", rc);
-@@ -357,21 +369,28 @@ pagedresults_free_one(Connection *conn, Operation *op, int index)
- }
- 
- /*
-- * Used for abandoning - pageresult_lock_get_addr(conn) is already locked in do_abandone.
-+ * Free one paged result entry by message ID.
-+ *
-+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
-+ * caller already holds pageresult_lock. Never call when holding c_mutex.
-  */
- int
--pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, pthread_mutex_t *mutex)
-+pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, bool locked)
- {
-     int rc = -1;
-     int i;
-+    pthread_mutex_t *lock = NULL;
- 
-     if (conn && (msgid > -1)) {
-         if (conn->c_pagedresults.prl_maxlen <= 0) {
-             ; /* Not a paged result. */
-         } else {
-             slapi_log_err(SLAPI_LOG_TRACE,
--                          "pagedresults_free_one_msgid_nolock", "=> msgid=%d\n", msgid);
--            pthread_mutex_lock(mutex);
-+                          "pagedresults_free_one_msgid", "=> msgid=%d\n", msgid);
-+            lock = pageresult_lock_get_addr(conn);
-+            if (!locked) {
-+                pthread_mutex_lock(lock);
-+            }
-             for (i = 0; i < conn->c_pagedresults.prl_maxlen; i++) {
-                 if (conn->c_pagedresults.prl_list[i].pr_msgid == msgid) {
-                     PagedResults *prp = conn->c_pagedresults.prl_list + i;
-@@ -390,9 +409,11 @@ pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, pthread_mutex_t *
-                     break;
-                 }
-             }
--            pthread_mutex_unlock(mutex);
-+            if (!locked) {
-+                pthread_mutex_unlock(lock);
-+            }
-             slapi_log_err(SLAPI_LOG_TRACE,
--                          "pagedresults_free_one_msgid_nolock", "<= %d\n", rc);
-+                          "pagedresults_free_one_msgid", "<= %d\n", rc);
-         }
-     }
- 
-@@ -418,29 +439,43 @@ pagedresults_get_current_be(Connection *conn, int index)
-     return be;
- }
- 
-+/*
-+ * Set current backend for a paged result entry.
-+ *
-+ * Locking: If locked=false, acquires pageresult_lock. If locked=true, assumes
-+ * caller already holds pageresult_lock. Never call when holding c_mutex.
-+ */
- int
--pagedresults_set_current_be(Connection *conn, Slapi_Backend *be, int index, int nolock)
-+pagedresults_set_current_be(Connection *conn, Slapi_Backend *be, int index, bool locked)
- {
-     int rc = -1;
-     slapi_log_err(SLAPI_LOG_TRACE,
-                   "pagedresults_set_current_be", "=> idx=%d\n", index);
-     if (conn && (index > -1)) {
--        if (!nolock)
-+        if (!locked) {
-             pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        }
-         if (index < conn->c_pagedresults.prl_maxlen) {
-             conn->c_pagedresults.prl_list[index].pr_current_be = be;
-         }
-         rc = 0;
--        if (!nolock)
-+        if (!locked) {
-             pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        }
-     }
-     slapi_log_err(SLAPI_LOG_TRACE,
-                   "pagedresults_set_current_be", "<= %d\n", rc);
-     return rc;
- }
- 
-+/*
-+ * Get search result set for a paged result entry.
-+ *
-+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
-+ * caller already holds pageresult_lock. Never call when holding c_mutex.
-+ */
- void *
--pagedresults_get_search_result(Connection *conn, Operation *op, int locked, int index)
-+pagedresults_get_search_result(Connection *conn, Operation *op, bool locked, int index)
- {
-     void *sr = NULL;
-     if (!op_is_pagedresults(op)) {
-@@ -465,8 +500,14 @@ pagedresults_get_search_result(Connection *conn, Operation *op, int locked, int
-     return sr;
- }
- 
-+/*
-+ * Set search result set for a paged result entry.
-+ *
-+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
-+ * caller already holds pageresult_lock. Never call when holding c_mutex.
-+ */
- int
--pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, int locked, int index)
-+pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, bool locked, int index)
- {
-     int rc = -1;
-     if (!op_is_pagedresults(op)) {
-@@ -494,8 +535,14 @@ pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, int lo
-     return rc;
- }
- 
-+/*
-+ * Get search result count for a paged result entry.
-+ *
-+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
-+ * caller already holds pageresult_lock. Never call when holding c_mutex.
-+ */
- int
--pagedresults_get_search_result_count(Connection *conn, Operation *op, int index)
-+pagedresults_get_search_result_count(Connection *conn, Operation *op, bool locked, int index)
- {
-     int count = 0;
-     if (!op_is_pagedresults(op)) {
-@@ -504,19 +551,29 @@ pagedresults_get_search_result_count(Connection *conn, Operation *op, int index)
-     slapi_log_err(SLAPI_LOG_TRACE,
-                   "pagedresults_get_search_result_count", "=> idx=%d\n", index);
-     if (conn && (index > -1)) {
--        pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        }
-         if (index < conn->c_pagedresults.prl_maxlen) {
-             count = conn->c_pagedresults.prl_list[index].pr_search_result_count;
-         }
--        pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        }
-     }
-     slapi_log_err(SLAPI_LOG_TRACE,
-                   "pagedresults_get_search_result_count", "<= %d\n", count);
-     return count;
- }
- 
-+/*
-+ * Set search result count for a paged result entry.
-+ *
-+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
-+ * caller already holds pageresult_lock. Never call when holding c_mutex.
-+ */
- int
--pagedresults_set_search_result_count(Connection *conn, Operation *op, int count, int index)
-+pagedresults_set_search_result_count(Connection *conn, Operation *op, int count, bool locked, int index)
- {
-     int rc = -1;
-     if (!op_is_pagedresults(op)) {
-@@ -525,11 +582,15 @@ pagedresults_set_search_result_count(Connection *conn, Operation *op, int count,
-     slapi_log_err(SLAPI_LOG_TRACE,
-                   "pagedresults_set_search_result_count", "=> idx=%d\n", index);
-     if (conn && (index > -1)) {
--        pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        }
-         if (index < conn->c_pagedresults.prl_maxlen) {
-             conn->c_pagedresults.prl_list[index].pr_search_result_count = count;
-         }
--        pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        }
-         rc = 0;
-     }
-     slapi_log_err(SLAPI_LOG_TRACE,
-@@ -537,9 +598,16 @@ pagedresults_set_search_result_count(Connection *conn, Operation *op, int count,
-     return rc;
- }
- 
-+/*
-+ * Get search result set size estimate for a paged result entry.
-+ *
-+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
-+ * caller already holds pageresult_lock. Never call when holding c_mutex.
-+ */
- int
- pagedresults_get_search_result_set_size_estimate(Connection *conn,
-                                                  Operation *op,
-+                                                 bool locked,
-                                                  int index)
- {
-     int count = 0;
-@@ -550,11 +618,15 @@ pagedresults_get_search_result_set_size_estimate(Connection *conn,
-                   "pagedresults_get_search_result_set_size_estimate",
-                   "=> idx=%d\n", index);
-     if (conn && (index > -1)) {
--        pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        }
-         if (index < conn->c_pagedresults.prl_maxlen) {
-             count = conn->c_pagedresults.prl_list[index].pr_search_result_set_size_estimate;
-         }
--        pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        }
-     }
-     slapi_log_err(SLAPI_LOG_TRACE,
-                   "pagedresults_get_search_result_set_size_estimate", "<= %d\n",
-@@ -562,10 +634,17 @@ pagedresults_get_search_result_set_size_estimate(Connection *conn,
-     return count;
- }
- 
-+/*
-+ * Set search result set size estimate for a paged result entry.
-+ *
-+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
-+ * caller already holds pageresult_lock. Never call when holding c_mutex.
-+ */
- int
- pagedresults_set_search_result_set_size_estimate(Connection *conn,
-                                                  Operation *op,
-                                                  int count,
-+                                                 bool locked,
-                                                  int index)
- {
-     int rc = -1;
-@@ -576,11 +655,15 @@ pagedresults_set_search_result_set_size_estimate(Connection *conn,
-                   "pagedresults_set_search_result_set_size_estimate",
-                   "=> idx=%d\n", index);
-     if (conn && (index > -1)) {
--        pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        }
-         if (index < conn->c_pagedresults.prl_maxlen) {
-             conn->c_pagedresults.prl_list[index].pr_search_result_set_size_estimate = count;
-         }
--        pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        }
-         rc = 0;
-     }
-     slapi_log_err(SLAPI_LOG_TRACE,
-@@ -589,8 +672,14 @@ pagedresults_set_search_result_set_size_estimate(Connection *conn,
-     return rc;
- }
- 
-+/*
-+ * Get with_sort flag for a paged result entry.
-+ *
-+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
-+ * caller already holds pageresult_lock. Never call when holding c_mutex.
-+ */
- int
--pagedresults_get_with_sort(Connection *conn, Operation *op, int index)
-+pagedresults_get_with_sort(Connection *conn, Operation *op, bool locked, int index)
- {
-     int flags = 0;
-     if (!op_is_pagedresults(op)) {
-@@ -599,19 +688,29 @@ pagedresults_get_with_sort(Connection *conn, Operation *op, int index)
-     slapi_log_err(SLAPI_LOG_TRACE,
-                   "pagedresults_get_with_sort", "=> idx=%d\n", index);
-     if (conn && (index > -1)) {
--        pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        }
-         if (index < conn->c_pagedresults.prl_maxlen) {
-             flags = conn->c_pagedresults.prl_list[index].pr_flags & CONN_FLAG_PAGEDRESULTS_WITH_SORT;
-         }
--        pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        }
-     }
-     slapi_log_err(SLAPI_LOG_TRACE,
-                   "pagedresults_get_with_sort", "<= %d\n", flags);
-     return flags;
- }
- 
-+/*
-+ * Set with_sort flag for a paged result entry.
-+ *
-+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
-+ * caller already holds pageresult_lock. Never call when holding c_mutex.
-+ */
- int
--pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, int index)
-+pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, bool locked, int index)
- {
-     int rc = -1;
-     if (!op_is_pagedresults(op)) {
-@@ -620,14 +719,18 @@ pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, int index
-     slapi_log_err(SLAPI_LOG_TRACE,
-                   "pagedresults_set_with_sort", "=> idx=%d\n", index);
-     if (conn && (index > -1)) {
--        pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_lock(pageresult_lock_get_addr(conn));
-+        }
-         if (index < conn->c_pagedresults.prl_maxlen) {
-             if (flags & OP_FLAG_SERVER_SIDE_SORTING) {
-                 conn->c_pagedresults.prl_list[index].pr_flags |=
-                     CONN_FLAG_PAGEDRESULTS_WITH_SORT;
-             }
-         }
--        pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        if (!locked) {
-+            pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-+        }
-         rc = 0;
-     }
-     slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_set_with_sort", "<= %d\n", rc);
-@@ -802,10 +905,6 @@ pagedresults_cleanup(Connection *conn, int needlock)
-             rc = 1;
-         }
-         prp->pr_current_be = NULL;
--        if (prp->pr_mutex) {
--            PR_DestroyLock(prp->pr_mutex);
--            prp->pr_mutex = NULL;
--        }
-         memset(prp, '\0', sizeof(PagedResults));
-     }
-     conn->c_pagedresults.prl_count = 0;
-@@ -840,10 +939,6 @@ pagedresults_cleanup_all(Connection *conn, int needlock)
-                 i < conn->c_pagedresults.prl_maxlen;
-          i++) {
-         prp = conn->c_pagedresults.prl_list + i;
--        if (prp->pr_mutex) {
--            PR_DestroyLock(prp->pr_mutex);
--            prp->pr_mutex = NULL;
--        }
-         if (prp->pr_current_be && prp->pr_search_result_set &&
-             prp->pr_current_be->be_search_results_release) {
-             prp->pr_current_be->be_search_results_release(&(prp->pr_search_result_set));
-@@ -1010,43 +1105,8 @@ op_set_pagedresults(Operation *op)
-     op->o_flags |= OP_FLAG_PAGED_RESULTS;
- }
- 
--/*
-- * pagedresults_lock/unlock -- introduced to protect search results for the
-- * asynchronous searches. Do not call these functions while the PR conn lock
-- * is held (e.g. pageresult_lock_get_addr(conn))
-- */
--void
--pagedresults_lock(Connection *conn, int index)
--{
--    PagedResults *prp;
--    if (!conn || (index < 0) || (index >= conn->c_pagedresults.prl_maxlen)) {
--        return;
--    }
--    pthread_mutex_lock(pageresult_lock_get_addr(conn));
--    prp = conn->c_pagedresults.prl_list + index;
--    if (prp->pr_mutex) {
--        PR_Lock(prp->pr_mutex);
--    }
--    pthread_mutex_unlock(pageresult_lock_get_addr(conn));
--}
--
--void
--pagedresults_unlock(Connection *conn, int index)
--{
--    PagedResults *prp;
--    if (!conn || (index < 0) || (index >= conn->c_pagedresults.prl_maxlen)) {
--        return;
--    }
--    pthread_mutex_lock(pageresult_lock_get_addr(conn));
--    prp = conn->c_pagedresults.prl_list + index;
--    if (prp->pr_mutex) {
--        PR_Unlock(prp->pr_mutex);
--    }
--    pthread_mutex_unlock(pageresult_lock_get_addr(conn));
--}
--
- int
--pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int index)
-+pagedresults_is_abandoned_or_notavailable(Connection *conn, bool locked, int index)
- {
-     PagedResults *prp;
-     int32_t result;
-@@ -1066,7 +1126,7 @@ pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int inde
- }
- 
- int
--pagedresults_set_search_result_pb(Slapi_PBlock *pb, void *sr, int locked)
-+pagedresults_set_search_result_pb(Slapi_PBlock *pb, void *sr, bool locked)
- {
-     int rc = -1;
-     Connection *conn = NULL;
-diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
-index 6af6583a5..8445670d2 100644
---- a/ldap/servers/slapd/proto-slap.h
-+++ b/ldap/servers/slapd/proto-slap.h
-@@ -1611,20 +1611,22 @@ pthread_mutex_t *pageresult_lock_get_addr(Connection *conn);
- int pagedresults_parse_control_value(Slapi_PBlock *pb, struct berval *psbvp, ber_int_t *pagesize, int *index, Slapi_Backend *be);
- void pagedresults_set_response_control(Slapi_PBlock *pb, int iscritical, ber_int_t estimate, int curr_search_count, int index);
- Slapi_Backend *pagedresults_get_current_be(Connection *conn, int index);
--int pagedresults_set_current_be(Connection *conn, Slapi_Backend *be, int index, int nolock);
--void *pagedresults_get_search_result(Connection *conn, Operation *op, int locked, int index);
--int pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, int locked, int index);
--int pagedresults_get_search_result_count(Connection *conn, Operation *op, int index);
--int pagedresults_set_search_result_count(Connection *conn, Operation *op, int cnt, int index);
-+int pagedresults_set_current_be(Connection *conn, Slapi_Backend *be, int index, bool locked);
-+void *pagedresults_get_search_result(Connection *conn, Operation *op, bool locked, int index);
-+int pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, bool locked, int index);
-+int pagedresults_get_search_result_count(Connection *conn, Operation *op, bool locked, int index);
-+int pagedresults_set_search_result_count(Connection *conn, Operation *op, int cnt, bool locked, int index);
- int pagedresults_get_search_result_set_size_estimate(Connection *conn,
-                                                      Operation *op,
-+                                                     bool locked,
-                                                      int index);
- int pagedresults_set_search_result_set_size_estimate(Connection *conn,
-                                                      Operation *op,
-                                                      int cnt,
-+                                                     bool locked,
-                                                      int index);
--int pagedresults_get_with_sort(Connection *conn, Operation *op, int index);
--int pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, int index);
-+int pagedresults_get_with_sort(Connection *conn, Operation *op, bool locked, int index);
-+int pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, bool locked, int index);
- int pagedresults_get_unindexed(Connection *conn, Operation *op, int index);
- int pagedresults_set_unindexed(Connection *conn, Operation *op, int index);
- int pagedresults_get_sort_result_code(Connection *conn, Operation *op, int index);
-@@ -1636,15 +1638,13 @@ int pagedresults_cleanup(Connection *conn, int needlock);
- int pagedresults_is_timedout_nolock(Connection *conn);
- int pagedresults_reset_timedout_nolock(Connection *conn);
- int pagedresults_in_use_nolock(Connection *conn);
--int pagedresults_free_one(Connection *conn, Operation *op, int index);
--int pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, pthread_mutex_t *mutex);
-+int pagedresults_free_one(Connection *conn, Operation *op, bool locked, int index);
-+int pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, bool locked);
- int op_is_pagedresults(Operation *op);
- int pagedresults_cleanup_all(Connection *conn, int needlock);
- void op_set_pagedresults(Operation *op);
--void pagedresults_lock(Connection *conn, int index);
--void pagedresults_unlock(Connection *conn, int index);
--int pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int index);
--int pagedresults_set_search_result_pb(Slapi_PBlock *pb, void *sr, int locked);
-+int pagedresults_is_abandoned_or_notavailable(Connection *conn, bool locked, int index);
-+int pagedresults_set_search_result_pb(Slapi_PBlock *pb, void *sr, bool locked);
- 
- /*
-  * sort.c
-diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
-index 49cfb4210..abb0d2e47 100644
---- a/ldap/servers/slapd/slap.h
-+++ b/ldap/servers/slapd/slap.h
-@@ -89,6 +89,10 @@ static char ptokPBE[34] = "Internal (Software) Token        ";
- #include <stdbool.h>
- #include <time.h> /* For timespec definitions */
- 
-+/* Macros for paged results lock parameter */
-+#define PR_LOCKED true
-+#define PR_NOT_LOCKED false
-+
- /* Provides our int types and platform specific requirements. */
- #include <slapi_pal.h>
- 
-@@ -1669,7 +1673,6 @@ typedef struct _paged_results
-     struct timespec pr_timelimit_hr;        /* expiry time of this request rel to clock monotonic */
-     int pr_flags;
-     ber_int_t pr_msgid; /* msgid of the request; to abandon */
--    PRLock *pr_mutex;   /* protect each conn structure    */
- } PagedResults;
- 
- /* array of simple paged structure stashed in connection */
--- 
-2.52.0
-

diff --git a/0007-Issue-7108-Fix-shutdown-crash-in-entry-cache-destruc.patch b/0007-Issue-7108-Fix-shutdown-crash-in-entry-cache-destruc.patch
deleted file mode 100644
index 3935e95..0000000
--- a/0007-Issue-7108-Fix-shutdown-crash-in-entry-cache-destruc.patch
+++ /dev/null
@@ -1,183 +0,0 @@
-From cde999edf7246d9dcec4a13950e2c0895165a16e Mon Sep 17 00:00:00 2001
-From: Simon Pichugin <spichugi@redhat.com>
-Date: Thu, 8 Jan 2026 10:02:39 -0800
-Subject: [PATCH] Issue 7108 - Fix shutdown crash in entry cache destruction
- (#7163)
-
-Description: The entry cache could experience LRU list corruption when
-using pinned entries, leading to crashes during cache flush operations.
-
-In entrycache_add_int(), when returning an existing cached entry, the
-code checked the wrong entry's state before calling lru_delete(). It
-checked the new entry 'e' but operated on the existing entry 'my_alt',
-causing lru_delete() to be called on entries not in the LRU list. This
-is fixed by checking my_alt's refcnt and pinned state instead.
-
-In flush_hash(), pinned_remove() and lru_delete() were both called on
-pinned entries. Since pinned entries are in the pinned list, calling
-lru_delete() afterwards corrupted the list. This is fixed by calling
-either pinned_remove() or lru_delete() based on the entry's state.
-
-A NULL check is added in entrycache_flush() and dncache_flush() to
-gracefully handle corrupted LRU lists and prevent crashes when
-traversing backwards through the list encounters an unexpected NULL.
-
-Entry pointers are now always cleared after lru_delete() removal to
-prevent stale pointer issues in non-debug builds.
-
-Fixes: https://github.com/389ds/389-ds-base/issues/7108
-
-Reviewed by: @progier389, @vashirov (Thanks!!)
----
- ldap/servers/slapd/back-ldbm/cache.c | 48 +++++++++++++++++++++++++---
- 1 file changed, 43 insertions(+), 5 deletions(-)
-
-diff --git a/ldap/servers/slapd/back-ldbm/cache.c b/ldap/servers/slapd/back-ldbm/cache.c
-index 2e4126134..a87f30687 100644
---- a/ldap/servers/slapd/back-ldbm/cache.c
-+++ b/ldap/servers/slapd/back-ldbm/cache.c
-@@ -458,11 +458,13 @@ static void
- lru_delete(struct cache *cache, void *ptr)
- {
-     struct backcommon *e;
-+
-     if (NULL == ptr) {
-         LOG("=> lru_delete\n<= lru_delete (null entry)\n");
-         return;
-     }
-     e = (struct backcommon *)ptr;
-+
- #ifdef LDAP_CACHE_DEBUG_LRU
-     pinned_verify(cache, __LINE__);
-     lru_verify(cache, e, 1);
-@@ -475,8 +477,9 @@ lru_delete(struct cache *cache, void *ptr)
-         e->ep_lrunext->ep_lruprev = e->ep_lruprev;
-     else
-         cache->c_lrutail = e->ep_lruprev;
--#ifdef LDAP_CACHE_DEBUG_LRU
-+    /* Always clear pointers after removal to prevent stale pointer issues */
-     e->ep_lrunext = e->ep_lruprev = NULL;
-+#ifdef LDAP_CACHE_DEBUG_LRU
-     lru_verify(cache, e, 0);
- #endif
- }
-@@ -633,9 +636,14 @@ flush_hash(struct cache *cache, struct timespec *start_time, int32_t type)
-                 if (entry->ep_refcnt == 0) {
-                     entry->ep_refcnt++;
-                     if (entry->ep_state & ENTRY_STATE_PINNED) {
-+                        /* Entry is in pinned list, not LRU - remove from pinned only.
-+                         * pinned_remove clears lru pointers and won't add to LRU since refcnt > 0.
-+                         */
-                         pinned_remove(cache, laste);
-+                    } else {
-+                        /* Entry is in LRU list - remove from LRU */
-+                        lru_delete(cache, laste);
-                     }
--                    lru_delete(cache, laste);
-                     if (type == ENTRY_CACHE) {
-                         entrycache_remove_int(cache, laste);
-                         entrycache_return(cache, (struct backentry **)&laste, PR_TRUE);
-@@ -679,9 +687,14 @@ flush_hash(struct cache *cache, struct timespec *start_time, int32_t type)
-                     if (entry->ep_refcnt == 0) {
-                         entry->ep_refcnt++;
-                         if (entry->ep_state & ENTRY_STATE_PINNED) {
-+                            /* Entry is in pinned list, not LRU - remove from pinned only.
-+                             * pinned_remove clears lru pointers and won't add to LRU since refcnt > 0.
-+                             */
-                             pinned_remove(cache, laste);
-+                        } else {
-+                            /* Entry is in LRU list - remove from LRU */
-+                            lru_delete(cache, laste);
-                         }
--                        lru_delete(cache, laste);
-                         entrycache_remove_int(cache, laste);
-                         entrycache_return(cache, (struct backentry **)&laste, PR_TRUE);
-                     } else {
-@@ -772,6 +785,11 @@ entrycache_flush(struct cache *cache)
-         } else {
-             e = BACK_LRU_PREV(e, struct backentry *);
-         }
-+        if (e == NULL) {
-+            slapi_log_err(SLAPI_LOG_WARNING, "entrycache_flush",
-+                          "Unexpected NULL entry while flushing cache - LRU list may be corrupted\n");
-+            break;
-+        }
-         ASSERT(e->ep_refcnt == 0);
-         e->ep_refcnt++;
-         if (entrycache_remove_int(cache, e) < 0) {
-@@ -1160,6 +1178,7 @@ pinned_remove(struct cache *cache, void *ptr)
- {
-     struct backentry *e = (struct backentry *)ptr;
-     ASSERT(e->ep_state & ENTRY_STATE_PINNED);
-+
-     cache->c_pinned_ctx->npinned--;
-     cache->c_pinned_ctx->size -= e->ep_size;
-     e->ep_state &= ~ENTRY_STATE_PINNED;
-@@ -1172,13 +1191,23 @@ pinned_remove(struct cache *cache, void *ptr)
-             cache->c_pinned_ctx->head = cache->c_pinned_ctx->tail = NULL;
-         } else {
-             cache->c_pinned_ctx->head = BACK_LRU_NEXT(e, struct backentry *);
-+            /* Update new head's prev pointer to NULL */
-+            if (cache->c_pinned_ctx->head) {
-+                cache->c_pinned_ctx->head->ep_lruprev = NULL;
-+            }
-         }
-     } else if (cache->c_pinned_ctx->tail == e) {
-         cache->c_pinned_ctx->tail = BACK_LRU_PREV(e, struct backentry *);
-+        /* Update new tail's next pointer to NULL */
-+        if (cache->c_pinned_ctx->tail) {
-+            cache->c_pinned_ctx->tail->ep_lrunext = NULL;
-+        }
-     } else {
-+        /* Middle of list: update both neighbors to point to each other */
-         BACK_LRU_PREV(e, struct backentry *)->ep_lrunext = BACK_LRU_NEXT(e, struct backcommon *);
-         BACK_LRU_NEXT(e, struct backentry *)->ep_lruprev = BACK_LRU_PREV(e, struct backcommon *);
-     }
-+    /* Clear the removed entry's pointers */
-     e->ep_lrunext = e->ep_lruprev = NULL;
-     if (e->ep_refcnt == 0) {
-         lru_add(cache, ptr);
-@@ -1245,6 +1274,7 @@ pinned_add(struct cache *cache, void *ptr)
-             return false;
-     }
-     /* Now it is time to insert the entry in the pinned list */
-+
-     cache->c_pinned_ctx->npinned++;
-     cache->c_pinned_ctx->size += e->ep_size;
-     e->ep_state |= ENTRY_STATE_PINNED;
-@@ -1754,7 +1784,7 @@ entrycache_add_int(struct cache *cache, struct backentry *e, int state, struct b
-                  * 3) ep_state: 0 && state: 0
-                  *    ==> increase the refcnt
-                  */
--                if (e->ep_refcnt == 0)
-+                if (e->ep_refcnt == 0 && (e->ep_state & ENTRY_STATE_PINNED) == 0)
-                     lru_delete(cache, (void *)e);
-                 e->ep_refcnt++;
-                 e->ep_state &= ~ENTRY_STATE_UNAVAILABLE;
-@@ -1781,7 +1811,7 @@ entrycache_add_int(struct cache *cache, struct backentry *e, int state, struct b
-             } else {
-                 if (alt) {
-                     *alt = my_alt;
--                    if (e->ep_refcnt == 0 && (e->ep_state & ENTRY_STATE_PINNED) == 0)
-+                    if (my_alt->ep_refcnt == 0 && (my_alt->ep_state & ENTRY_STATE_PINNED) == 0)
-                         lru_delete(cache, (void *)*alt);
-                     (*alt)->ep_refcnt++;
-                     LOG("the entry %s already exists.  returning existing entry %s (state: 0x%x)\n",
-@@ -2379,6 +2409,14 @@ dncache_flush(struct cache *cache)
-         } else {
-             dn = BACK_LRU_PREV(dn, struct backdn *);
-         }
-+        if (dn == NULL) {
-+            /* Safety check: we should normally exit via the CACHE_LRU_HEAD check.
-+             * If we get here, c_lruhead may be NULL or the LRU list is corrupted.
-+             */
-+            slapi_log_err(SLAPI_LOG_WARNING, "dncache_flush",
-+                          "Unexpected NULL entry while flushing cache - LRU list may be corrupted\n");
-+            break;
-+        }
-         ASSERT(dn->ep_refcnt == 0);
-         dn->ep_refcnt++;
-         if (dncache_remove_int(cache, dn) < 0) {
--- 
-2.52.0
-

diff --git a/0008-Issue-7172-Index-ordering-mismatch-after-upgrade-717.patch b/0008-Issue-7172-Index-ordering-mismatch-after-upgrade-717.patch
deleted file mode 100644
index 038c6c3..0000000
--- a/0008-Issue-7172-Index-ordering-mismatch-after-upgrade-717.patch
+++ /dev/null
@@ -1,215 +0,0 @@
-From 062aa6eab12d00adffa4e46d58722f6c0e5eeac1 Mon Sep 17 00:00:00 2001
-From: Viktor Ashirov <vashirov@redhat.com>
-Date: Fri, 9 Jan 2026 11:39:50 +0100
-Subject: [PATCH] Issue 7172 - Index ordering mismatch after upgrade (#7173)
-
-Bug Description:
-Commit daf731f55071d45eaf403a52b63d35f4e699ff28 introduced a regression.
-After upgrading to a version that adds `integerOrderingMatch` matching
-rule to `parentid` and `ancestorid` indexes, searches may return empty
-or incorrect results.
-
-This happens because the existing index data was created with
-lexicographic ordering, but the new compare function expects integer
-ordering. Index lookups fail because the compare function doesn't match
-the data ordering.
-The root cause is that `ldbm_instance_create_default_indexes()` calls
-`attr_index_config()` unconditionally for `parentid` and `ancestorid`
-indexes, which triggers `ainfo_dup()` to overwrite `ai_key_cmp_fn` on
-existing indexes. This breaks indexes that were created without the
-`integerOrderingMatch` matching rule.
-
-Fix Description:
-* Call `attr_index_config()` for `parentid` and `ancestorid` indexes
-only if index config doesn't exist.
-
-* Add `upgrade_check_id_index_matching_rule()` that logs an error on
-server startup if `parentid` or `ancestorid` indexes are missing the
-integerOrderingMatch matching rule, advising administrators to reindex.
-
-Fixes: https://github.com/389ds/389-ds-base/issues/7172
-
-Reviewed by: @tbordaz, @progier389, @droideck (Thanks!)
----
- ldap/servers/slapd/back-ldbm/instance.c |  25 ++++--
- ldap/servers/slapd/upgrade.c            | 107 +++++++++++++++++++++++-
- 2 files changed, 123 insertions(+), 9 deletions(-)
-
-diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
-index cb002c379..71bf0f6fa 100644
---- a/ldap/servers/slapd/back-ldbm/instance.c
-+++ b/ldap/servers/slapd/back-ldbm/instance.c
-@@ -190,6 +190,7 @@ ldbm_instance_create_default_indexes(backend *be)
-     char *ancestorid_indexes_limit = NULL;
-     char *parentid_indexes_limit = NULL;
-     struct attrinfo *ai = NULL;
-+    struct attrinfo *index_already_configured = NULL;
-     struct index_idlistsizeinfo *iter;
-     int cookie;
-     int limit;
-@@ -248,10 +249,14 @@ ldbm_instance_create_default_indexes(backend *be)
-     ldbm_instance_config_add_index_entry(inst, e, flags);
-     slapi_entry_free(e);
- 
--    e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit);
--    ldbm_instance_config_add_index_entry(inst, e, flags);
--    attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
--    slapi_entry_free(e);
-+    ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
-+    index_already_configured = ai;
-+    if (!index_already_configured) {
-+        e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit);
-+        ldbm_instance_config_add_index_entry(inst, e, flags);
-+        attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
-+        slapi_entry_free(e);
-+    }
- 
-     e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0, 0);
-     ldbm_instance_config_add_index_entry(inst, e, flags);
-@@ -288,10 +293,14 @@ ldbm_instance_create_default_indexes(backend *be)
-      * ancestorid is special, there is actually no such attr type
-      * but we still want to use the attr index file APIs.
-      */
--    e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit);
--    ldbm_instance_config_add_index_entry(inst, e, flags);
--    attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
--    slapi_entry_free(e);
-+    ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
-+    index_already_configured = ai;
-+    if (!index_already_configured) {
-+        e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit);
-+        ldbm_instance_config_add_index_entry(inst, e, flags);
-+        attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
-+        slapi_entry_free(e);
-+    }
- 
-     slapi_ch_free_string(&ancestorid_indexes_limit);
-     slapi_ch_free_string(&parentid_indexes_limit);
-diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
-index 858392564..b02e37ed6 100644
---- a/ldap/servers/slapd/upgrade.c
-+++ b/ldap/servers/slapd/upgrade.c
-@@ -330,6 +330,107 @@ upgrade_remove_subtree_rename(void)
-     return UPGRADE_SUCCESS;
- }
- 
-+/*
-+ * Check if parentid/ancestorid indexes are missing the integerOrderingMatch
-+ * matching rule.
-+ *
-+ * This function logs a warning if we detect this condition, advising
-+ * the administrator to reindex the affected attributes.
-+ */
-+static upgrade_status
-+upgrade_check_id_index_matching_rule(void)
-+{
-+    struct slapi_pblock *pb = slapi_pblock_new();
-+    Slapi_Entry **backends = NULL;
-+    const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
-+    const char *be_filter = "(objectclass=nsBackendInstance)";
-+    const char *attrs_to_check[] = {"parentid", "ancestorid", NULL};
-+    upgrade_status uresult = UPGRADE_SUCCESS;
-+
-+    /* Search for all backend instances */
-+    slapi_search_internal_set_pb(
-+            pb, be_base_dn,
-+            LDAP_SCOPE_ONELEVEL,
-+            be_filter, NULL, 0, NULL, NULL,
-+            plugin_get_default_component_id(), 0);
-+    slapi_search_internal_pb(pb);
-+    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
-+
-+    if (backends) {
-+        for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
-+            const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
-+            if (!be_name) {
-+                continue;
-+            }
-+
-+            /* Check each attribute that should have integerOrderingMatch */
-+            for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
-+                const char *attr_name = attrs_to_check[attr_idx];
-+                struct slapi_pblock *idx_pb = slapi_pblock_new();
-+                Slapi_Entry **idx_entries = NULL;
-+                char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,%s",
-+                                                       attr_name, be_name, be_base_dn);
-+                char *idx_filter = "(objectclass=nsIndex)";
-+                PRBool has_matching_rule = PR_FALSE;
-+
-+                if (!idx_dn) {
-+                    slapi_pblock_destroy(idx_pb);
-+                    continue;
-+                }
-+
-+                slapi_search_internal_set_pb(
-+                        idx_pb, idx_dn,
-+                        LDAP_SCOPE_BASE,
-+                        idx_filter, NULL, 0, NULL, NULL,
-+                        plugin_get_default_component_id(), 0);
-+                slapi_search_internal_pb(idx_pb);
-+                slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
-+
-+                if (idx_entries && idx_entries[0]) {
-+                    /* Index exists, check if it has integerOrderingMatch */
-+                    Slapi_Attr *mr_attr = NULL;
-+                    if (slapi_entry_attr_find(idx_entries[0], "nsMatchingRule", &mr_attr) == 0) {
-+                        Slapi_Value *sval = NULL;
-+                        int idx;
-+                        for (idx = slapi_attr_first_value(mr_attr, &sval);
-+                             idx != -1;
-+                             idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
-+                            const struct berval *bval = slapi_value_get_berval(sval);
-+                            if (bval && bval->bv_val &&
-+                                strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
-+                                has_matching_rule = PR_TRUE;
-+                                break;
-+                            }
-+                        }
-+                    }
-+
-+                    if (!has_matching_rule) {
-+                        /* Index exists but doesn't have integerOrderingMatch, log a warning */
-+                        slapi_log_err(SLAPI_LOG_ERR, "upgrade_check_id_index_matching_rule",
-+                                "Index '%s' in backend '%s' is missing 'nsMatchingRule: integerOrderingMatch'. "
-+                                "Incorrectly configured system indexes can lead to poor search performance, replication issues, and other operational problems. "
-+                                "To fix this, add the matching rule and reindex: "
-+                                "dsconf <instance> backend index set --add-mr integerOrderingMatch --attr %s %s && "
-+                                "dsconf <instance> backend index reindex --attr %s %s. "
-+                                "WARNING: Reindexing can be resource-intensive and may impact server performance on a live system. "
-+                                "Consider scheduling reindexing during maintenance windows or periods of low activity.\n",
-+                                attr_name, be_name, attr_name, be_name, attr_name, be_name);
-+                    }
-+                }
-+
-+                slapi_ch_free_string(&idx_dn);
-+                slapi_free_search_results_internal(idx_pb);
-+                slapi_pblock_destroy(idx_pb);
-+            }
-+        }
-+    }
-+
-+    slapi_free_search_results_internal(pb);
-+    slapi_pblock_destroy(pb);
-+
-+    return uresult;
-+}
-+
- /*
-  * Upgrade the base config of the PAM PTA plugin.
-  *
-@@ -547,7 +648,11 @@ upgrade_server(void)
-     if (upgrade_pam_pta_default_config() != UPGRADE_SUCCESS) {
-         return UPGRADE_FAILURE;
-     }
--    
-+
-+    if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
-+        return UPGRADE_FAILURE;
-+    }
-+
-     return UPGRADE_SUCCESS;
- }
- 
--- 
-2.52.0
-

diff --git a/0009-Issue-7172-2nd-Index-ordering-mismatch-after-upgrade.patch b/0009-Issue-7172-2nd-Index-ordering-mismatch-after-upgrade.patch
deleted file mode 100644
index 442fb27..0000000
--- a/0009-Issue-7172-2nd-Index-ordering-mismatch-after-upgrade.patch
+++ /dev/null
@@ -1,67 +0,0 @@
-From 6bce8f6e8c985289c4ac1a4f051c291283c0a1ec Mon Sep 17 00:00:00 2001
-From: Viktor Ashirov <vashirov@redhat.com>
-Date: Mon, 12 Jan 2026 10:58:02 +0100
-Subject: [PATCH 9/9] Issue 7172 - (2nd) Index ordering mismatch after upgrade
- (#7180)
-
-Commit 742c12e0247ab64e87da000a4de2f3e5c99044ab introduced a regression
-where the check to skip creating parentid/ancestorid indexes if they
-already exist was incorrect.
-The `ainfo_get()` function falls back to returning
-LDBM_PSEUDO_ATTR_DEFAULT attrinfo when the requested attribute is not
-found.
-Since LDBM_PSEUDO_ATTR_DEFAULT is created before the ancestorid check,
-`ainfo_get()` returns LDBM_PSEUDO_ATTR_DEFAULT instead of NULL, causing
-the ancestorid index creation to be skipped entirely.
-
-When operations later try to use the ancestorid index, they fall back to
-LDBM_PSEUDO_ATTR_DEFAULT, and attempting to open the .default dbi
-mid-transaction fails with MDB_NOTFOUND (-30798).
-
-Fix Description:
-Instead of just checking if `ainfo_get()` returns non-NULL, verify that
-the returned attrinfo is actually for the requested attribute.
-
-Fixes: https://github.com/389ds/389-ds-base/issues/7172
-
-Reviewed by: @tbordaz (Thanks!)
----
- ldap/servers/slapd/back-ldbm/instance.c | 8 +++++---
- 1 file changed, 5 insertions(+), 3 deletions(-)
-
-diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
-index 71bf0f6fa..2a6e8cbb8 100644
---- a/ldap/servers/slapd/back-ldbm/instance.c
-+++ b/ldap/servers/slapd/back-ldbm/instance.c
-@@ -190,7 +190,7 @@ ldbm_instance_create_default_indexes(backend *be)
-     char *ancestorid_indexes_limit = NULL;
-     char *parentid_indexes_limit = NULL;
-     struct attrinfo *ai = NULL;
--    struct attrinfo *index_already_configured = NULL;
-+    int index_already_configured = 0;
-     struct index_idlistsizeinfo *iter;
-     int cookie;
-     int limit;
-@@ -250,7 +250,8 @@ ldbm_instance_create_default_indexes(backend *be)
-     slapi_entry_free(e);
- 
-     ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
--    index_already_configured = ai;
-+    /* Check if the attrinfo is actually for parentid, not a fallback to .default */
-+    index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_PARENTID_STR) == 0);
-     if (!index_already_configured) {
-         e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit);
-         ldbm_instance_config_add_index_entry(inst, e, flags);
-@@ -294,7 +295,8 @@ ldbm_instance_create_default_indexes(backend *be)
-      * but we still want to use the attr index file APIs.
-      */
-     ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
--    index_already_configured = ai;
-+    /* Check if the attrinfo is actually for ancestorid, not a fallback to .default */
-+    index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_ANCESTORID_STR) == 0);
-     if (!index_already_configured) {
-         e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit);
-         ldbm_instance_config_add_index_entry(inst, e, flags);
--- 
-2.52.0
-

diff --git a/0010-Bump-lodash-from-4.17.21-to-4.17.23-in-src-cockpit-3.patch b/0010-Bump-lodash-from-4.17.21-to-4.17.23-in-src-cockpit-3.patch
deleted file mode 100644
index d90aa5d..0000000
--- a/0010-Bump-lodash-from-4.17.21-to-4.17.23-in-src-cockpit-3.patch
+++ /dev/null
@@ -1,56 +0,0 @@
-From a6d18806b5b65aba5c0e0b686619de782e420d65 Mon Sep 17 00:00:00 2001
-From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
-Date: Wed, 21 Jan 2026 19:58:46 -0800
-Subject: [PATCH] Bump lodash from 4.17.21 to 4.17.23 in
- /src/cockpit/389-console (#7203)
-
-Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23.
-- [Release notes](https://github.com/lodash/lodash/releases)
-- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)
-
----
-updated-dependencies:
-- dependency-name: lodash
-  dependency-version: 4.17.23
-  dependency-type: indirect
-...
-
-Signed-off-by: dependabot[bot] <support@github.com>
-Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
----
- src/cockpit/389-console/package-lock.json | 12 ++++++------
- 1 file changed, 6 insertions(+), 6 deletions(-)
-
-diff --git a/src/cockpit/389-console/package-lock.json b/src/cockpit/389-console/package-lock.json
-index 0aa5bbbb9..23faef62f 100644
---- a/src/cockpit/389-console/package-lock.json
-+++ b/src/cockpit/389-console/package-lock.json
-@@ -4833,9 +4833,9 @@
-       }
-     },
-     "node_modules/lodash": {
--      "version": "4.17.21",
--      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
--      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
-+      "version": "4.17.23",
-+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
-+      "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
-     },
-     "node_modules/lodash.merge": {
-       "version": "4.6.2",
-@@ -11087,9 +11087,9 @@
-       }
-     },
-     "lodash": {
--      "version": "4.17.21",
--      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
--      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
-+      "version": "4.17.23",
-+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
-+      "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
-     },
-     "lodash.merge": {
-       "version": "4.6.2",
--- 
-2.52.0
-

diff --git a/0011-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch b/0011-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch
deleted file mode 100644
index a4d01cc..0000000
--- a/0011-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch
+++ /dev/null
@@ -1,235 +0,0 @@
-From f4a899a7b7764059da56be2835d0b3e93b4a84b4 Mon Sep 17 00:00:00 2001
-From: Viktor Ashirov <vashirov@redhat.com>
-Date: Tue, 20 Jan 2026 09:52:47 +0100
-Subject: [PATCH] Issue 7189 - DSBLE0007 generates incorrect remediation
- commands for scan limits
-
-Bug Description:
-
-The generated dsconf commands for fixing missing system indexes had two issues:
-
-1. The --add-scanlimit value was not quoted, causing the shell to interpret
-   "limit=5000 type=eq flags=AND" as multiple arguments instead of a single
-   value, resulting in "unrecognized arguments: type=eq flags=AND" error.
-
-2. When both matching rule and scanlimit were missing, two separate commands
-   were generated where the second would fail because the matching rule was
-   already added by the first command.
-
-Fix Description:
-
-1. Quote the scanlimit value in all remediation commands
-
-2. Combine matching rule and scanlimit fixes into a single command when
-   both are missing for the same index instead of expected_scanlimit)
-
-Fixes: https://github.com/389ds/389-ds-base/issues/7189
-
-Reviewed by: @progier389, @droideck (Thanks!)
----
- .../healthcheck/health_system_indexes_test.py | 126 ++++++++++++++++++
- src/lib389/lib389/backend.py                  |  39 +++---
- 2 files changed, 147 insertions(+), 18 deletions(-)
-
-diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-index a977b71d1..486fad44b 100644
---- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-@@ -408,6 +408,132 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable
-     run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
- 
- 
-+def test_missing_scanlimit(topology_st, log_buffering_enabled):
-+    """Check if healthcheck returns DSBLE0007 code when parentId index is missing scanlimit
-+
-+    :id: 40e1bf6a-2397-459b-bdf3-f787ca118b86
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create DS instance
-+        2. Remove nsIndexIDListScanLimit from parentId index
-+        3. Use healthcheck without --json option
-+        4. Use healthcheck with --json option
-+        5. Verify the remediation command has properly quoted scanlimit
-+        6. Re-add the scanlimit
-+        7. Use healthcheck without --json option
-+        8. Use healthcheck with --json option
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. healthcheck reports DSBLE0007 code and related details
-+        4. healthcheck reports DSBLE0007 code and related details
-+        5. The scanlimit value is quoted in the remediation command
-+        6. Success
-+        7. healthcheck reports no issues found
-+        8. healthcheck reports no issues found
-+    """
-+
-+    RET_CODE = "DSBLE0007"
-+    PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
-+    SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
-+
-+    standalone = topology_st.standalone
-+
-+    log.info("Remove nsIndexIDListScanLimit from parentId index")
-+    parentid_index = Index(standalone, PARENTID_DN)
-+    parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
-+
-+    run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
-+
-+    # Verify the remediation command has properly quoted scanlimit
-+    args = FakeArgs()
-+    args.instance = standalone.serverid
-+    args.verbose = standalone.verbose
-+    args.list_errors = False
-+    args.list_checks = False
-+    args.exclude_check = []
-+    args.check = ["backends"]
-+    args.dry_run = False
-+    args.json = False
-+    health_check_run(standalone, topology_st.logcap.log, args)
-+    # Check that the scanlimit is quoted in the output
-+    assert topology_st.logcap.contains('--add-scanlimit "limit=5000 type=eq flags=AND"')
-+    log.info("Verified scanlimit is properly quoted in remediation command")
-+    topology_st.logcap.flush()
-+
-+    run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
-+
-+    log.info("Re-add the nsIndexIDListScanLimit")
-+    parentid_index = Index(standalone, PARENTID_DN)
-+    parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
-+
-+    run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
-+    run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
-+
-+
-+def test_missing_matching_rule_and_scanlimit(topology_st, log_buffering_enabled):
-+    """Check if healthcheck generates a single combined command when both matching rule and scanlimit are missing
-+
-+    :id: af8214ad-5e4c-422a-8f74-3e99227551df
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create DS instance
-+        2. Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index
-+        3. Use healthcheck and verify a single combined command is generated
-+        4. Re-add the matching rule and scanlimit
-+        5. Use healthcheck without --json option
-+        6. Use healthcheck with --json option
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. healthcheck reports DSBLE0007 and generates a single command with both --add-mr and --add-scanlimit
-+        4. Success
-+        5. healthcheck reports no issues found
-+        6. healthcheck reports no issues found
-+    """
-+
-+    RET_CODE = "DSBLE0007"
-+    PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
-+    SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
-+
-+    standalone = topology_st.standalone
-+
-+    log.info("Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index")
-+    parentid_index = Index(standalone, PARENTID_DN)
-+    parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
-+    parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
-+
-+    # Run healthcheck and verify combined command
-+    args = FakeArgs()
-+    args.instance = standalone.serverid
-+    args.verbose = standalone.verbose
-+    args.list_errors = False
-+    args.list_checks = False
-+    args.exclude_check = []
-+    args.check = ["backends"]
-+    args.dry_run = False
-+    args.json = False
-+    health_check_run(standalone, topology_st.logcap.log, args)
-+
-+    # Verify DSBLE0007 is reported
-+    assert topology_st.logcap.contains(RET_CODE)
-+    log.info("healthcheck returned code: %s" % RET_CODE)
-+
-+    # Verify a single combined command is generated with both --add-mr and --add-scanlimit
-+    assert topology_st.logcap.contains('--add-mr integerOrderingMatch --add-scanlimit "limit=5000 type=eq flags=AND"')
-+    log.info("Verified combined command with both --add-mr and --add-scanlimit")
-+
-+    topology_st.logcap.flush()
-+
-+    log.info("Re-add the integerOrderingMatch matching rule and scanlimit")
-+    parentid_index = Index(standalone, PARENTID_DN)
-+    parentid_index.add("nsMatchingRule", "integerOrderingMatch")
-+    parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
-+
-+    run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
-+    run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
-+
-+
- def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
-     """Check if healthcheck returns DSBLE0007 code when multiple system indexes are missing
- 
-diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
-index fba95987b..db464b43a 100644
---- a/src/lib389/lib389/backend.py
-+++ b/src/lib389/lib389/backend.py
-@@ -678,7 +678,7 @@ class Backend(DSLdapObject):
-                     if expected_config.get('matching_rule'):
-                         cmd += f" --matching-rule {expected_config['matching_rule']}"
-                     if expected_config.get('scanlimit'):
--                        cmd += f" --add-scanlimit {expected_config['scanlimit']}"
-+                        cmd += f" --add-scanlimit \"{expected_config['scanlimit']}\""
-                     remediation_commands.append(cmd)
-                     reindex_attrs.add(attr_name)  # New index needs reindexing
-                 else:
-@@ -700,28 +700,31 @@ class Backend(DSLdapObject):
-                         remediation_commands.append(cmd)
-                         reindex_attrs.add(attr_name)
- 
--                    # Check matching rules
-+                    # Check matching rules and scanlimit together to generate a single combined command
-                     expected_mr = expected_config.get('matching_rule')
-+                    expected_scanlimit = expected_config.get('scanlimit')
-+
-+                    missing_mr = False
-                     if expected_mr:
-                         actual_mrs_lower = [mr.lower() for mr in actual_mrs]
-                         if expected_mr.lower() not in actual_mrs_lower:
-                             discrepancies.append(f"Index {attr_name} missing matching rule: {expected_mr}")
--                            # Add the missing matching rule
--                            cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr}"
--                            remediation_commands.append(cmd)
--                            reindex_attrs.add(attr_name)
--
--                    # Check fine grain definitions for parentid ONLY
--                    expected_scanlimit = expected_config.get('scanlimit')
--                    if (attr_name.lower() == "parentid") and expected_scanlimit and (len(actual_scanlimit) == 0):
--                            discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_mr}")
--                            # Add the missing scanlimit
--                            if expected_mr:
--                                cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr} --add-scanlimit {expected_scanlimit}"
--                            else:
--                                cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-scanlimit {expected_scanlimit}"
--                            remediation_commands.append(cmd)
--                            reindex_attrs.add(attr_name)
-+                            missing_mr = True
-+
-+                    missing_scanlimit = False
-+                    if expected_scanlimit and (len(actual_scanlimit) == 0):
-+                        discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_scanlimit}")
-+                        missing_scanlimit = True
-+
-+                    # Generate a single combined command for all missing items
-+                    if missing_mr or missing_scanlimit:
-+                        cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name}"
-+                        if missing_mr:
-+                            cmd += f" --add-mr {expected_mr}"
-+                        if missing_scanlimit:
-+                            cmd += f" --add-scanlimit \"{expected_scanlimit}\""
-+                        remediation_commands.append(cmd)
-+                        reindex_attrs.add(attr_name)
- 
-             except Exception as e:
-                 self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}")
--- 
-2.52.0
-

diff --git a/0012-Issue-7198-Web-console-doesn-t-show-sub-suffix-when-.patch b/0012-Issue-7198-Web-console-doesn-t-show-sub-suffix-when-.patch
deleted file mode 100644
index 0043373..0000000
--- a/0012-Issue-7198-Web-console-doesn-t-show-sub-suffix-when-.patch
+++ /dev/null
@@ -1,517 +0,0 @@
-From 8d87a9dba330e8a584bcc9dc9710099bcf720339 Mon Sep 17 00:00:00 2001
-From: Simon Pichugin <spichugi@redhat.com>
-Date: Fri, 23 Jan 2026 17:35:45 -0800
-Subject: [PATCH] Issue 7198 - Web console doesn't show sub-suffix when
- parent-suffix points to an entry (#7202)
-
-Description: The web console doesn't show sub-suffixes when the
-nsslapd-parent-suffix attribute points to an entry rather than a backend
-suffix.
-For example, creating a sub-suffix ou=foo,ou=people,dc=example,dc=com
-with parent-suffix ou=people,dc=example,dc=com (where ou=people is just an
-entry, not a suffix) would not appear in the web console tree.
-
-Fix: In backend_build_tree() and get_sub_suffixes(), the code only matched
-when nsslapd-parent-suffix exactly equaled an existing backend suffix.
-Now it also checks if the parent-suffix is an entry under the current
-suffix (ends with ,suffix) and is not itself a backend suffix. This
-correctly attaches sub-suffixes to their containing suffix when the
-parent-suffix points to an intermediate entry.
-
-Fixes: https://github.com/389ds/389-ds-base/issues/7198
-
-Reviewed by: @progier389 (Thanks!)
----
- .../suites/lib389/subsuffix_tree_test.py      | 313 ++++++++++++++++++
- src/lib389/lib389/backend.py                  |  47 ++-
- src/lib389/lib389/cli_conf/backend.py         |  34 +-
- 3 files changed, 370 insertions(+), 24 deletions(-)
- create mode 100644 dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py
-
-diff --git a/dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py b/dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py
-new file mode 100644
-index 000000000..fa10ba530
---- /dev/null
-+++ b/dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py
-@@ -0,0 +1,313 @@
-+# --- BEGIN COPYRIGHT BLOCK ---
-+# Copyright (C) 2026 Red Hat, Inc.
-+# All rights reserved.
-+#
-+# License: GPL (version 3 or any later version).
-+# See LICENSE for details.
-+# --- END COPYRIGHT BLOCK ---
-+#
-+import logging
-+import os
-+import pytest
-+from lib389.topologies import topology_st as topo
-+from lib389.backend import Backends
-+from lib389.idm.organizationalunit import OrganizationalUnits
-+from lib389._constants import DEFAULT_SUFFIX
-+
-+pytestmark = pytest.mark.tier1
-+
-+logging.getLogger(__name__).setLevel(logging.INFO)
-+log = logging.getLogger(__name__)
-+
-+
-+@pytest.fixture(scope="function")
-+def setup_subsuffix_with_entry_parent(topo, request):
-+    """Setup a sub-suffix whose parent-suffix points to an entry, not a suffix."""
-+    inst = topo.standalone
-+
-+    # Create ou=people entry under the root suffix
-+    log.info("Creating ou=people,dc=example,dc=com entry")
-+    ous = OrganizationalUnits(inst, DEFAULT_SUFFIX)
-+    ou_people = ous.get('people')
-+
-+    # Create sub-suffix with parent-suffix pointing to the entry
-+    log.info("Creating sub-suffix ou=foo,ou=people,dc=example,dc=com")
-+    backends = Backends(inst)
-+    subsuffix_dn = 'ou=foo,ou=people,dc=example,dc=com'
-+    parent_suffix_dn = 'ou=people,dc=example,dc=com'
-+
-+    foo_backend = backends.create(properties={
-+        'cn': 'foo',
-+        'nsslapd-suffix': subsuffix_dn,
-+        'parent': parent_suffix_dn,
-+    })
-+
-+    # Create the suffix entry
-+    foo_ous = OrganizationalUnits(inst, parent_suffix_dn)
-+    foo_ou = foo_ous.create(properties={'ou': 'foo'})
-+
-+    def cleanup():
-+        log.info("Cleaning up test backends and entries")
-+        try:
-+            foo_ou.delete()
-+        except Exception as e:
-+            log.warning(f"Failed to delete foo_ou: {e}")
-+        try:
-+            foo_backend.delete()
-+        except Exception as e:
-+            log.warning(f"Failed to delete foo_backend: {e}")
-+
-+    request.addfinalizer(cleanup)
-+
-+    return {
-+        'instance': inst,
-+        'backends': backends,
-+        'foo_backend': foo_backend,
-+        'ou_people': ou_people,
-+        'subsuffix_dn': subsuffix_dn,
-+        'parent_suffix_dn': parent_suffix_dn,
-+    }
-+
-+
-+def test_subsuffix_with_entry_parent_in_tree(topo, setup_subsuffix_with_entry_parent):
-+    """Test that a sub-suffix with parent pointing to an entry is visible in the tree.
-+
-+    :id: 256f36f5-76ad-4043-ad8d-1f9e2afc4e1d
-+    :setup: Standalone instance with sub-suffix whose parent is an entry
-+    :steps:
-+        1. Verify the sub-suffix backend exists
-+        2. Get sub-suffixes of the root backend
-+        3. Verify the sub-suffix appears in the list
-+    :expectedresults:
-+        1. Backend should exist
-+        2. Sub-suffixes should be retrievable
-+        3. Sub-suffix should be visible (this is where the bug manifested)
-+    """
-+    backends = setup_subsuffix_with_entry_parent['backends']
-+    foo_backend = setup_subsuffix_with_entry_parent['foo_backend']
-+    subsuffix_dn = setup_subsuffix_with_entry_parent['subsuffix_dn']
-+
-+    # Step 1: Verify the sub-suffix backend exists
-+    assert foo_backend.exists(), "The foo backend should exist"
-+
-+    # Step 2: Get sub-suffixes of the root backend
-+    root_backend = backends.get(DEFAULT_SUFFIX)
-+    sub_suffixes = root_backend.get_sub_suffixes()
-+    log.info(f"Sub-suffixes found: {[s.get_attr_val_utf8('nsslapd-suffix') for s in sub_suffixes]}")
-+
-+    # Step 3: Verify sub-suffix is in the list
-+    sub_suffix_found = any(
-+        s.get_attr_val_utf8_l('nsslapd-suffix') == subsuffix_dn.lower()
-+        for s in sub_suffixes
-+    )
-+
-+    assert sub_suffix_found, (
-+        f"Sub-suffix {subsuffix_dn} should be visible in get_sub_suffixes(). "
-+        "The parent-suffix points to an entry, not a backend suffix."
-+    )
-+
-+
-+def test_subsuffix_in_backend_list(topo, setup_subsuffix_with_entry_parent):
-+    """Test that the sub-suffix appears in the backend list.
-+
-+    :id: 0ccc49af-91bb-4e8f-b0e1-1bd0b75c041b
-+    :setup: Standalone instance with sub-suffix configuration
-+    :steps:
-+        1. Get all backends
-+        2. Verify both root suffix and sub-suffix are present
-+    :expectedresults:
-+        1. Should retrieve all backends
-+        2. Both suffixes should be listed
-+    """
-+    backends = setup_subsuffix_with_entry_parent['backends']
-+    subsuffix_dn = setup_subsuffix_with_entry_parent['subsuffix_dn']
-+
-+    be_list = backends.list()
-+    suffixes = [be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_list]
-+
-+    assert DEFAULT_SUFFIX.lower() in suffixes, \
-+        f"Root suffix {DEFAULT_SUFFIX} should be in the list"
-+    assert subsuffix_dn.lower() in suffixes, \
-+        f"Sub-suffix {subsuffix_dn} should be in the list"
-+
-+
-+def test_subsuffix_dn_boundary_matching():
-+    """Test that suffix matching respects DN component boundaries.
-+
-+    :id: 0b856e26-c394-4c36-b9ba-d7894aa2ed11
-+    :setup: None (unit test)
-+    :steps:
-+        1. Test exact suffix match
-+        2. Test proper DN ancestor match (ends with ,suffix)
-+        3. Test that partial string matches are rejected
-+    :expectedresults:
-+        1. Exact match should return True
-+        2. Proper ancestor should return True
-+        3. Partial string match should return False
-+    """
-+    from lib389.backend import is_subsuffix_of
-+
-+    all_suffixes = {'dc=com', 'dc=example,dc=com', 'ou=dept,dc=example,dc=com'}
-+
-+    # Test 1: Exact match
-+    assert is_subsuffix_of('dc=example,dc=com', 'dc=example,dc=com', all_suffixes), \
-+        "Exact match should return True"
-+
-+    # Test 2: Parent is an entry under the suffix (not itself a suffix)
-+    assert is_subsuffix_of('ou=people,dc=example,dc=com', 'dc=example,dc=com', all_suffixes), \
-+        "Parent entry under suffix should return True"
-+
-+    # Test 3: Parent IS a suffix - should return False (handled separately)
-+    assert not is_subsuffix_of('ou=dept,dc=example,dc=com', 'dc=example,dc=com', all_suffixes), \
-+        "Parent that is itself a suffix should return False"
-+
-+    # Test 4: Edge case - wrong DN boundary (string ends with suffix but wrong boundary)
-+    edge_suffixes = {'dc=com', 'st,dc=com'}
-+    assert is_subsuffix_of('dc=test,dc=com', 'dc=com', edge_suffixes), \
-+        "dc=test,dc=com should match dc=com"
-+    assert not is_subsuffix_of('dc=test,dc=com', 'st,dc=com', edge_suffixes), \
-+        "dc=test,dc=com should NOT match st,dc=com (wrong DN boundary)"
-+
-+    # Test 5: None input
-+    assert not is_subsuffix_of(None, 'dc=com', all_suffixes), \
-+        "None parent should return False"
-+
-+    # Test 6: Closest ancestor - should only match the nearest suffix
-+    # Hierarchy: dc=com -> dc=example,dc=com -> ou=branch,dc=example,dc=com (suffix)
-+    #            -> ou=dept,ou=branch,dc=example,dc=com (entry) -> subsuffix
-+    # The subsuffix should only appear under ou=branch, not under dc=example,dc=com
-+    nested_suffixes = {'dc=com', 'dc=example,dc=com', 'ou=branch,dc=example,dc=com'}
-+    entry_parent = 'ou=dept,ou=branch,dc=example,dc=com'
-+    # Should match ou=branch (closest)
-+    assert is_subsuffix_of(entry_parent, 'ou=branch,dc=example,dc=com', nested_suffixes), \
-+        "Should match closest ancestor suffix (ou=branch)"
-+    # Should NOT match dc=example,dc=com (not closest)
-+    assert not is_subsuffix_of(entry_parent, 'dc=example,dc=com', nested_suffixes), \
-+        "Should NOT match distant ancestor (dc=example) - ou=branch is closer"
-+    # Should NOT match dc=com (not closest)
-+    assert not is_subsuffix_of(entry_parent, 'dc=com', nested_suffixes), \
-+        "Should NOT match distant ancestor (dc=com) - ou=branch is closer"
-+
-+    log.info("All DN boundary edge cases passed")
-+
-+
-+def test_deep_suffix_hierarchy(topo, request):
-+    """Test complex hierarchy: suffix -> suffix -> entry -> suffix -> suffix.
-+
-+    :id: fd06491a-defa-4780-8472-78c077febdfb
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create sub-suffix ou=branch (parent=dc=example,dc=com - a suffix)
-+        2. Create entry ou=dept,ou=branch (not a suffix)
-+        3. Create sub-suffix ou=team,ou=dept,ou=branch (parent=ou=dept - an entry)
-+        4. Create sub-suffix ou=sub,ou=team,ou=dept,ou=branch (parent=ou=team - a suffix)
-+        5. Verify all sub-suffixes are correctly placed in the tree
-+    :expectedresults:
-+        1. Sub-suffix created successfully
-+        2. Entry created successfully
-+        3. Sub-suffix with entry parent created successfully
-+        4. Sub-suffix with suffix parent created successfully
-+        5. Tree hierarchy is correct
-+    """
-+    inst = topo.standalone
-+    backends = Backends(inst)
-+
-+    # Define the hierarchy
-+    branch_suffix = f'ou=branch,{DEFAULT_SUFFIX}'
-+    dept_entry = f'ou=dept,{branch_suffix}'  # This is an ENTRY, not a suffix
-+    team_suffix = f'ou=team,{dept_entry}'
-+    sub_suffix = f'ou=sub,{team_suffix}'
-+
-+    created_backends = []
-+    created_entries = []
-+
-+    def cleanup():
-+        log.info("Cleaning up deep hierarchy test")
-+        for entry in reversed(created_entries):
-+            try:
-+                entry.delete()
-+            except Exception as e:
-+                log.warning(f"Failed to delete entry: {e}")
-+        for be in reversed(created_backends):
-+            try:
-+                be.delete()
-+            except Exception as e:
-+                log.warning(f"Failed to delete backend: {e}")
-+
-+    request.addfinalizer(cleanup)
-+
-+    # Step 1: Create ou=branch sub-suffix (parent is root suffix)
-+    log.info(f"Creating sub-suffix {branch_suffix}")
-+    branch_be = backends.create(properties={
-+        'cn': 'branch',
-+        'nsslapd-suffix': branch_suffix,
-+        'parent': DEFAULT_SUFFIX,
-+    })
-+    created_backends.append(branch_be)
-+    branch_ous = OrganizationalUnits(inst, DEFAULT_SUFFIX)
-+    branch_ou = branch_ous.create(properties={'ou': 'branch'})
-+    created_entries.append(branch_ou)
-+
-+    # Step 2: Create ou=dept entry under branch (NOT a suffix)
-+    log.info(f"Creating entry {dept_entry}")
-+    dept_ous = OrganizationalUnits(inst, branch_suffix)
-+    dept_ou = dept_ous.create(properties={'ou': 'dept'})
-+    created_entries.append(dept_ou)
-+
-+    # Step 3: Create ou=team sub-suffix (parent is dept ENTRY, not a suffix)
-+    log.info(f"Creating sub-suffix {team_suffix} with entry parent {dept_entry}")
-+    team_be = backends.create(properties={
-+        'cn': 'team',
-+        'nsslapd-suffix': team_suffix,
-+        'parent': dept_entry,  # Parent is an ENTRY!
-+    })
-+    created_backends.append(team_be)
-+    team_ous = OrganizationalUnits(inst, dept_entry)
-+    team_ou = team_ous.create(properties={'ou': 'team'})
-+    created_entries.append(team_ou)
-+
-+    # Step 4: Create ou=sub sub-suffix (parent is team suffix)
-+    log.info(f"Creating sub-suffix {sub_suffix} with suffix parent {team_suffix}")
-+    sub_be = backends.create(properties={
-+        'cn': 'sub',
-+        'nsslapd-suffix': sub_suffix,
-+        'parent': team_suffix,  # Parent is a SUFFIX
-+    })
-+    created_backends.append(sub_be)
-+    sub_ous = OrganizationalUnits(inst, team_suffix)
-+    sub_ou = sub_ous.create(properties={'ou': 'sub'})
-+    created_entries.append(sub_ou)
-+
-+    # Step 5: Verify the tree hierarchy
-+    log.info("Verifying tree hierarchy...")
-+
-+    # Root should have branch as sub-suffix
-+    root_be = backends.get(DEFAULT_SUFFIX)
-+    root_subs = root_be.get_sub_suffixes()
-+    root_sub_suffixes = [s.get_attr_val_utf8_l('nsslapd-suffix') for s in root_subs]
-+    log.info(f"Root sub-suffixes: {root_sub_suffixes}")
-+    assert branch_suffix.lower() in root_sub_suffixes, \
-+        f"branch should be under root suffix"
-+
-+    # Branch should have team as sub-suffix (even though team's parent is an entry)
-+    branch_be_obj = backends.get(branch_suffix)
-+    branch_subs = branch_be_obj.get_sub_suffixes()
-+    branch_sub_suffixes = [s.get_attr_val_utf8_l('nsslapd-suffix') for s in branch_subs]
-+    log.info(f"Branch sub-suffixes: {branch_sub_suffixes}")
-+    assert team_suffix.lower() in branch_sub_suffixes, \
-+        f"team should be under branch suffix (parent is entry under branch)"
-+
-+    # Team should have sub as sub-suffix
-+    team_be_obj = backends.get(team_suffix)
-+    team_subs = team_be_obj.get_sub_suffixes()
-+    team_sub_suffixes = [s.get_attr_val_utf8_l('nsslapd-suffix') for s in team_subs]
-+    log.info(f"Team sub-suffixes: {team_sub_suffixes}")
-+    assert sub_suffix.lower() in team_sub_suffixes, \
-+        f"sub should be under team suffix"
-+
-+    log.info("Deep hierarchy test passed!")
-+
-+
-+if __name__ == '__main__':
-+    CURRENT_FILE = os.path.realpath(__file__)
-+    pytest.main(["-s", CURRENT_FILE])
-diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
-index db464b43a..274d45abe 100644
---- a/src/lib389/lib389/backend.py
-+++ b/src/lib389/lib389/backend.py
-@@ -38,6 +38,36 @@ from lib389.lint import DSBLE0001, DSBLE0002, DSBLE0003, DSBLE0004, DSBLE0005, D
- from lib389.plugins import USNPlugin
- 
- 
-+def is_subsuffix_of(sub_parent, be_suffix, all_suffixes):
-+    """Check if sub_parent indicates this is a sub-suffix of be_suffix.
-+
-+    Returns True only if be_suffix is the CLOSEST ancestor suffix of sub_parent.
-+    This prevents a sub-suffix from appearing under multiple ancestors.
-+
-+    :param sub_parent: The nsslapd-parent-suffix value (lowercase)
-+    :param be_suffix: The suffix to check against (lowercase)
-+    :param all_suffixes: Set of all backend suffixes (lowercase)
-+    :returns: True if be_suffix is the closest ancestor suffix
-+    """
-+    if not sub_parent:
-+        return False
-+    if sub_parent == be_suffix:
-+        return True
-+    if sub_parent in all_suffixes:
-+        # sub_parent is itself a suffix, will be handled separately
-+        return False
-+    if not sub_parent.endswith(',' + be_suffix):
-+        return False
-+    # Find the closest (longest) matching suffix for this parent
-+    best_match = None
-+    for sfx in all_suffixes:
-+        if sub_parent == sfx or sub_parent.endswith(',' + sfx):
-+            if best_match is None or len(sfx) > len(best_match):
-+                best_match = sfx
-+    # Only return True if be_suffix is the closest match
-+    return best_match == be_suffix
-+
-+
- class BackendLegacy(object):
-     proxied_methods = 'search_s getEntry'.split()
- 
-@@ -1104,22 +1134,27 @@ class Backend(DSLdapObject):
-         vlv.create(rdn="cn=" + vlvname, properties=props, basedn=basedn)
- 
-     def get_sub_suffixes(self):
--        """Return a list of Backend's
--        returns: a List of subsuffix entries
-+        """Return a list of Backend's that are sub-suffixes of this backend.
-+        :returns: A list of Backend instances that are sub-suffixes
-         """
-         subsuffixes = []
-         top_be_suffix = self.get_attr_val_utf8_l('nsslapd-suffix')
-+        if not top_be_suffix:
-+            return subsuffixes
-+
-         mts = self._mts.list()
-+        be_insts = Backends(self._instance).list()
-+        all_suffixes = {be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_insts}
-+
-         for mt in mts:
-             parent_suffix = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
-             if parent_suffix is None:
-                 continue
--            if parent_suffix == top_be_suffix:
-+
-+            if is_subsuffix_of(parent_suffix, top_be_suffix, all_suffixes):
-                 child_suffix = mt.get_attr_val_utf8_l('cn')
--                be_insts = Backends(self._instance).list()
-                 for be in be_insts:
--                    be_suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
--                    if child_suffix == be_suffix:
-+                    if child_suffix == be.get_attr_val_utf8_l('nsslapd-suffix'):
-                         subsuffixes.append(be)
-                         break
-         return subsuffixes
-diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
-index d0ec4bd9e..9772e39d4 100644
---- a/src/lib389/lib389/cli_conf/backend.py
-+++ b/src/lib389/lib389/cli_conf/backend.py
-@@ -7,7 +7,7 @@
- # See LICENSE for details.
- # --- END COPYRIGHT BLOCK ---
- 
--from lib389.backend import Backend, Backends, DatabaseConfig, BackendSuffixView
-+from lib389.backend import Backend, Backends, DatabaseConfig, BackendSuffixView, is_subsuffix_of
- from lib389.configurations.sample import (
-     create_base_domain,
-     create_base_org,
-@@ -338,6 +338,7 @@ def is_db_replicated(inst, suffix):
- def backend_get_subsuffixes(inst, basedn, log, args):
-     subsuffixes = []
-     be_insts = MANY(inst).list()
-+    all_suffixes = {be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_insts}
-     for be in be_insts:
-         be_suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
-         if be_suffix == args.be_name.lower():
-@@ -347,7 +348,7 @@ def backend_get_subsuffixes(inst, basedn, log, args):
-                 db_type = "suffix"
-                 sub = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
-                 sub_be = mt.get_attr_val_utf8_l('nsslapd-backend')
--                if sub == be_suffix:
-+                if is_subsuffix_of(sub, be_suffix, all_suffixes):
-                     # We have a subsuffix (maybe a db link?)
-                     if is_db_link(inst, sub_be):
-                         db_type = "link"
-@@ -399,38 +400,34 @@ def build_node(suffix, be_name, subsuf=False, link=False, replicated=False):
-     }
- 
- 
--def backend_build_tree(inst, be_insts, nodes):
--    """Recursively build the tree
--    """
--    if len(nodes) == 0:
--        # Done
-+def backend_build_tree(inst, be_insts, nodes, all_suffixes):
-+    """Recursively build the tree."""
-+    if not nodes:
-         return
- 
-     for node in nodes:
--        node_suffix = node['id']
-+        node_suffix = node['id'].lower()
-         # Get sub suffixes and chaining of node
-         for be in be_insts:
-             be_suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
--            if be_suffix == node_suffix.lower():
-+            if be_suffix == node_suffix:
-                 # We have our parent, now find the children
-                 mts = be._mts.list()
--
-                 for mt in mts:
-                     sub_parent = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
-                     sub_be = mt.get_attr_val_utf8_l('nsslapd-backend')
-                     sub_suffix = mt.get_attr_val_utf8_l('cn')
--                    if sub_parent == be_suffix:
-+                    if is_subsuffix_of(sub_parent, be_suffix, all_suffixes):
-                         # We have a subsuffix (maybe a db link?)
-                         link = is_db_link(inst, sub_be)
-                         replicated = is_db_replicated(inst, sub_suffix)
-                         node['children'].append(build_node(sub_suffix,
--                                                        sub_be,
--                                                        subsuf=True,
--                                                        link=link,
--                                                        replicated=replicated))
--
-+                                                           sub_be,
-+                                                           subsuf=True,
-+                                                           link=link,
-+                                                           replicated=replicated))
-                 # Recurse over the new subsuffixes
--                backend_build_tree(inst, be_insts, node['children'])
-+                backend_build_tree(inst, be_insts, node['children'], all_suffixes)
-                 break
- 
- 
-@@ -471,7 +468,8 @@ def backend_get_tree(inst, basedn, log, args):
-     else:
-         # Build the tree
-         be_insts = Backends(inst).list()
--        backend_build_tree(inst, be_insts, nodes)
-+        all_suffixes = {be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_insts}
-+        backend_build_tree(inst, be_insts, nodes, all_suffixes)
- 
-         # Done
-         if args.json:
--- 
-2.52.0
-

diff --git a/0013-Issue-7184-argparse.HelpFormatter-_format_actions_us.patch b/0013-Issue-7184-argparse.HelpFormatter-_format_actions_us.patch
deleted file mode 100644
index 2acbcf4..0000000
--- a/0013-Issue-7184-argparse.HelpFormatter-_format_actions_us.patch
+++ /dev/null
@@ -1,48 +0,0 @@
-From 98229fe9cde11aede104dfc669274e4e3745b4e8 Mon Sep 17 00:00:00 2001
-From: Mark Reynolds <mreynolds@redhat.com>
-Date: Mon, 12 Jan 2026 13:53:05 -0500
-Subject: [PATCH] Issue 7184 - argparse.HelpFormatter _format_actions_usage()
- is deprecated
-
-Description:
-
-_format_actions_usage() was removed in python 3.15. Instead we can use
-_get_actions_usage_parts() but it also behaves differently between
-python 3.14 and 3.15 so we need special handling.
-
-Relates: https://github.com/389ds/389-ds-base/issues/7184
-
-Reviewed by: spichugi(Thanks!)
----
- src/lib389/lib389/cli_base/__init__.py | 15 ++++++++++++++-
- 1 file changed, 14 insertions(+), 1 deletion(-)
-
-diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py
-index 06b8f9964..f1055aadc 100644
---- a/src/lib389/lib389/cli_base/__init__.py
-+++ b/src/lib389/lib389/cli_base/__init__.py
-@@ -413,7 +413,20 @@ class CustomHelpFormatter(argparse.HelpFormatter):
- 
-     def _format_usage(self, usage, actions, groups, prefix):
-         usage = super(CustomHelpFormatter, self)._format_usage(usage, actions, groups, prefix)
--        formatted_options = self._format_actions_usage(parent_arguments, [])
-+
-+        if sys.version_info < (3, 13):
-+            # Use _format_actions_usage() for Python 3.12 and earlier
-+            formatted_options = self._format_actions_usage(parent_arguments, [])
-+        else:
-+            # Use _get_actions_usage_parts() for Python 3.13 and later
-+            action_parts = self._get_actions_usage_parts(parent_arguments, [])
-+            if sys.version_info >= (3, 15):
-+                # Python 3.15 returns a tuple (list of actions, count of actions)
-+                formatted_options = ' '.join(action_parts[0])
-+            else:
-+                # Python 3.13 and 3.14 return a list of actions
-+                formatted_options = ' '.join(action_parts)
-+
-         # If formatted_options already in usage - remove them
-         if formatted_options in usage:
-             usage = usage.replace(f' {formatted_options}', '')
--- 
-2.52.0
-

diff --git a/0014-Issue-7027-2nd-389-ds-base-OpenScanHub-Leaks-Detecte.patch b/0014-Issue-7027-2nd-389-ds-base-OpenScanHub-Leaks-Detecte.patch
deleted file mode 100644
index 05b2c85..0000000
--- a/0014-Issue-7027-2nd-389-ds-base-OpenScanHub-Leaks-Detecte.patch
+++ /dev/null
@@ -1,53 +0,0 @@
-From b5c8dfe16456fbd9d360cec492f869f42f401053 Mon Sep 17 00:00:00 2001
-From: Viktor Ashirov <vashirov@redhat.com>
-Date: Fri, 30 Jan 2026 12:00:13 +0100
-Subject: [PATCH] Issue 7027 - (2nd) 389-ds-base OpenScanHub Leaks Detected
- (#7211)
-
-Fix Description:
-Update coverity annotations.
-
-Relates: https://github.com/389ds/389-ds-base/issues/7027
-
-Reviewed by: @aadhikar (Thanks!)
----
- ldap/servers/slapd/log.c | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
-index ea744ac1e..80c07382a 100644
---- a/ldap/servers/slapd/log.c
-+++ b/ldap/servers/slapd/log.c
-@@ -206,8 +206,8 @@ compress_log_file(char *log_name, int32_t mode)
- 
-     if ((source = fopen(log_name, "r")) == NULL) {
-         /* Failed to open log file */
--        /* coverity[leaked_storage] gzclose does close FD */
-         gzclose(outfile);
-+        /* coverity[leaked_handle] gzclose does close FD */
-         return -1;
-     }
- 
-@@ -217,17 +217,17 @@ compress_log_file(char *log_name, int32_t mode)
-         if (bytes_written == 0)
-         {
-             fclose(source);
--            /* coverity[leaked_storage] gzclose does close FD */
-             gzclose(outfile);
-+            /* coverity[leaked_handle] gzclose does close FD */
-             return -1;
-         }
-         bytes_read = fread(buf, 1, LOG_CHUNK, source);
-     }
--    /* coverity[leaked_storage] gzclose does close FD */
-     gzclose(outfile);
-     fclose(source);
-     PR_Delete(log_name); /* remove the old uncompressed log */
- 
-+    /* coverity[leaked_handle] gzclose does close FD */
-     return 0;
- }
- 
--- 
-2.52.0
-

diff --git a/0015-Issue-7213-MDB_BAD_VALSIZE-error-while-handling-VLV-.patch b/0015-Issue-7213-MDB_BAD_VALSIZE-error-while-handling-VLV-.patch
deleted file mode 100644
index e0e21bb..0000000
--- a/0015-Issue-7213-MDB_BAD_VALSIZE-error-while-handling-VLV-.patch
+++ /dev/null
@@ -1,197 +0,0 @@
-From 46ce38c02d0f86725829592bbff27679a625e9a0 Mon Sep 17 00:00:00 2001
-From: progier389 <progier@redhat.com>
-Date: Mon, 2 Feb 2026 15:39:18 +0100
-Subject: [PATCH] Issue 7213 - MDB_BAD_VALSIZE error while handling VLV (#7214)
-
-* Issue 7213 - MDB_BAD_VALSIZE error while handling VLV
-Avoid failing lmdb operation when handling VLV index by truncating the key so that key+data is small enough.
-
-Issue: #7213
-
-Reviewed by: @mreynolds389 , @vashirov (Thanks!)
-
-Assisted by: Claude A/I
-
-(cherry picked from commit 5ebce22d4214bec5ed94ad84c4448164be99389a)
----
- .../tests/suites/vlv/regression_test.py       | 110 ++++++++++++++++++
- .../slapd/back-ldbm/db-mdb/mdb_layer.c        |   5 +
- ldap/servers/slapd/back-ldbm/vlv.c            |   7 +-
- 3 files changed, 121 insertions(+), 1 deletion(-)
-
-diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
-index f7847ac74..7cdf16a84 100644
---- a/dirsrvtests/tests/suites/vlv/regression_test.py
-+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
-@@ -1175,6 +1175,116 @@ def test_vlv_with_mr(vlv_setup_with_uid_mr):
- 
- 
- 
-+def test_vlv_long_attribute_value(topology_st, request):
-+    """
-+    Test VLV with an entry containing a very long attribute value (2K).
-+
-+    :id: 99126fa4-003e-11f1-b7d6-c85309d5c3e3
-+    :setup: Standalone instance.
-+    :steps:
-+        1. Cleanup leftover from previous tests
-+        2. Create VLV search and index on cn attribute
-+        3. Reindex VLV
-+        4. Add an entry with a cn attribute having 2K character value
-+        5. Verify the entry was added successfully
-+        6. Perform a VLV search to ensure it still works
-+        7. Add another entry with a cn attribute having 2K character value
-+        8. Verify the entry was added successfully
-+        9. Perform a VLV search to ensure it still works
-+    :expectedresults:
-+        1. Should Success.
-+        2. Should Success.
-+        3. Should Success.
-+        4. Should Success.
-+        5. Should Success.
-+        6. Should Success.
-+        7. Should Success.
-+        8. Should Success.
-+        9. Should Success.
-+    """
-+    inst = topology_st.standalone
-+    reindex_task = Tasks(inst)
-+
-+    users_to_delete = []
-+
-+    def fin():
-+        cleanup(inst)
-+        # Clean the added users
-+        for user in users_to_delete:
-+            user.delete()
-+
-+    if not DEBUGGING:
-+        request.addfinalizer(fin)
-+
-+    # Clean previous tests leftover
-+    fin()
-+
-+    # Create VLV search and index
-+    vlv_search, vlv_index = create_vlv_search_and_index(inst)
-+    assert reindex_task.reindex(
-+        suffix=DEFAULT_SUFFIX,
-+        attrname=vlv_index.rdn,
-+        args={TASK_WAIT: True},
-+        vlv=True
-+    ) == 0
-+
-+    # Add a few regular users first
-+    add_users(inst, 10)
-+
-+    # Create a very long cn value (2K characters)
-+    long_cn_value = 'a' * 2048 + '1'
-+
-+    # Add an entry with the long cn attribute
-+    users = UserAccounts(inst, DEFAULT_SUFFIX)
-+    user_properties = {
-+        'uid': 'longcnuser1',
-+        'cn': long_cn_value,
-+        'sn': 'user1',
-+        'uidNumber': '99999',
-+        'gidNumber': '99999',
-+        'homeDirectory': '/home/longcnuser1'
-+    }
-+    user = users.create(properties=user_properties)
-+    users_to_delete.append(user);
-+
-+    # Verify the entry was created and has the long cn value
-+    entry = user.get_attr_vals_utf8('cn')
-+    assert entry[0] == long_cn_value
-+    log.info(f'Successfully created user with cn length: {len(entry[0])}')
-+
-+    # Perform VLV search to ensure VLV still works with long attribute values
-+    conn = open_new_ldapi_conn(inst.serverid)
-+    count = len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(uid=*)"))
-+    assert count > 0
-+    log.info(f'VLV search successful with {count} entries including entry with 2K cn value')
-+
-+    # Add another entry with the long cn attribute
-+    long_cn_value = 'a' * 2048 + '2'
-+
-+    user_properties = {
-+        'uid': 'longcnuser2',
-+        'cn': long_cn_value,
-+        'sn': 'user2',
-+        'uidNumber': '99998',
-+        'gidNumber': '99998',
-+        'homeDirectory': '/home/longcnuser2'
-+    }
-+    user = users.create(properties=user_properties)
-+    users_to_delete.append(user);
-+
-+    # Verify the entry was created and has the long cn value
-+    entry = user.get_attr_vals_utf8('cn')
-+    assert entry[0] == long_cn_value
-+    log.info(f'Successfully created user with cn length: {len(entry[0])}')
-+
-+    # Perform VLV search to ensure VLV still works with long attribute values
-+    conn = open_new_ldapi_conn(inst.serverid)
-+    count = len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(uid=*)"))
-+    assert count > 1
-+    log.info(f'VLV search successful with {count} entries including entry with 2K cn value')
-+
-+
-+
- if __name__ == "__main__":
-     # Run isolated
-     # -s for DEBUG mode
-diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
-index d320ecbeb..cd797621d 100644
---- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
-+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
-@@ -2134,10 +2134,15 @@ void *dbmdb_recno_cache_build(void *arg)
-         recno = 1;
-     }
-     while (rc == 0) {
-+        struct ldbminfo *li = (struct ldbminfo *)rcctx->cursor->be->be_database->plg_private;
-         slapi_log_err(SLAPI_LOG_DEBUG, "dbmdb_recno_cache_build", "recno=%d\n", recno);
-         if (recno % RECNO_CACHE_INTERVAL == 1) {
-             /* Prepare the cache data */
-             len = sizeof(*rce) + data.mv_size + key.mv_size;
-+            if (len > li->li_max_key_len) {
-+                key.mv_size = li->li_max_key_len - data.mv_size - sizeof(*rce);
-+                len = li->li_max_key_len;
-+            }
-             rce = (dbmdb_recno_cache_elmt_t*)slapi_ch_malloc(len);
-             rce->len = len;
-             rce->recno = recno;
-diff --git a/ldap/servers/slapd/back-ldbm/vlv.c b/ldap/servers/slapd/back-ldbm/vlv.c
-index 8f9263f25..f2f882b5a 100644
---- a/ldap/servers/slapd/back-ldbm/vlv.c
-+++ b/ldap/servers/slapd/back-ldbm/vlv.c
-@@ -866,6 +866,7 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct
-     struct vlv_key *key = NULL;
-     dbi_val_t data = {0};
-     dblayer_private *priv = NULL;
-+    size_t key_size_limit = li->li_max_key_len - sizeof(entry->ep_id);
- 
-     slapi_pblock_get(pb, SLAPI_BACKEND, &be);
-     priv = (dblayer_private *)li->li_dblayer_private;
-@@ -886,6 +887,10 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct
-         return rc;
-     }
- 
-+    /* Truncate the key if it is too long */
-+    if (key->key.size > key_size_limit) {
-+        key->key.size = key_size_limit;
-+    }
-     if (NULL != txn) {
-         db_txn = txn->back_txn_txn;
-     } else {
-@@ -930,7 +935,7 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct
-         if (txn && txn->back_special_handling_fn) {
-             rc = txn->back_special_handling_fn(be, BTXNACT_VLV_DEL, db, &key->key, &data, txn);
-         } else {
--            rc = dblayer_db_op(be, db, db_txn, DBI_OP_DEL, &key->key, NULL);
-+            rc = dblayer_db_op(be, db, db_txn, DBI_OP_DEL, &key->key, &data);
-         }
-         if (rc == 0) {
-             if (txn && txn->back_special_handling_fn) {
--- 
-2.52.0
-

diff --git a/0016-Issue-7194-Repl-Log-Analysis-Add-CSN-propagation-det.patch b/0016-Issue-7194-Repl-Log-Analysis-Add-CSN-propagation-det.patch
deleted file mode 100644
index 900f389..0000000
--- a/0016-Issue-7194-Repl-Log-Analysis-Add-CSN-propagation-det.patch
+++ /dev/null
@@ -1,1645 +0,0 @@
-From 64845ffd989313f9629bc1afe1052cfa0eef00b1 Mon Sep 17 00:00:00 2001
-From: Simon Pichugin <spichugi@redhat.com>
-Date: Tue, 3 Feb 2026 17:17:02 -0800
-Subject: [PATCH] Issue 7194 - Repl Log Analysis - Add CSN propagation details
- (#7195)
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Description: The replication log analyzer now shows per‑CSN propagation
-details and the console UI can drill into them from chart points. This
-adds CSN IDs to chart datapoints, builds detailed arrivals/hops data, and
-links replica IDs to origin servers for more accurate origin detection.
-
-The report JSON now includes csnDetails and sampling metadata; when
-sampling is active, CSN details are limited to sampled IDs to control
-memory use. A new originIncludedInArrivals flag is exposed and the UI
-shows an explicit note when origin records are outside the time range.
-The cockpit report modal gains an interactive CSN detail view and
-clickable chart points.
-
-Tests were expanded to cover CSN details, origin out‑of‑scope behavior,
-and partial replication, and include helper functions to reduce duplication.
-
-Fixes: https://github.com/389ds/389-ds-base/issues/7194
-
-Reviewed by: @progier389, @mreynolds389 (Thanks!!)
----
- .../replication/repl_log_monitoring_test.py   | 481 +++++++++++++++--
- .../src/lib/monitor/monitorModals.jsx         | 484 +++++++++++++++++-
- src/lib389/lib389/repltools.py                | 270 +++++++++-
- 3 files changed, 1166 insertions(+), 69 deletions(-)
-
-diff --git a/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py b/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py
-index 665fcb96f..855005ff9 100644
---- a/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py
-+++ b/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py
-@@ -21,7 +21,7 @@ from lib389.backend import Backends
- from lib389.topologies import topology_m4 as topo_m4
- from lib389.idm.user import UserAccount
- from lib389.replica import ReplicationManager
--from lib389.repltools import ReplicationLogAnalyzer
-+from lib389.repltools import ReplicationLogAnalyzer, DSLogParser
- from lib389._constants import *
- 
- pytestmark = pytest.mark.tier0
-@@ -105,6 +105,101 @@ def _cleanup_multi_suffix_test(test_users_by_suffix, tmp_dir, suppliers, extra_s
-         log.error(f"Error cleaning up temporary directory: {e}")
- 
- 
-+def _clear_access_logs(suppliers):
-+    """Clear access logs for all suppliers and restart."""
-+    for supplier in suppliers:
-+        supplier.deleteAccessLogs(restart=True)
-+
-+
-+def _restart_suppliers(suppliers):
-+    """Restart all suppliers."""
-+    for supplier in suppliers:
-+        supplier.restart()
-+
-+
-+def _get_log_dirs(suppliers):
-+    """Return log directories for all suppliers."""
-+    return [s.ds_paths.log_dir for s in suppliers]
-+
-+
-+def _load_json(path):
-+    """Load and return JSON from file."""
-+    with open(path, 'r') as f:
-+        return json.load(f)
-+
-+
-+def _pause_agreements(supplier, suffix):
-+    """Pause outbound agreements and return list of paused tuples."""
-+    paused = []
-+    for agmt in supplier.agreement.list(suffix=suffix):
-+        supplier.agreement.pause(agmt.dn)
-+        paused.append((supplier, agmt.dn))
-+    return paused
-+
-+
-+def _resume_agreements(paused_agreements):
-+    """Resume paused replication agreements."""
-+    for supplier_obj, dn in paused_agreements:
-+        try:
-+            supplier_obj.agreement.resume(dn)
-+        except Exception as e:
-+            log.warning(f"Failed to resume agreement {dn}: {e}")
-+
-+
-+def _assert_csn_details_schema(json_data):
-+    """Validate csnDetails presence and basic structure."""
-+    assert 'csnDetails' in json_data, "Expected csnDetails in JSON output for drill-down"
-+    csn_details = json_data['csnDetails']
-+    if csn_details:
-+        # Check structure of at least one CSN detail entry
-+        first_csn = next(iter(csn_details.values()))
-+        assert 'csn' in first_csn, "CSN detail should contain 'csn' field"
-+        assert 'targetDn' in first_csn, "CSN detail should contain 'targetDn' field"
-+        assert 'suffix' in first_csn, "CSN detail should contain 'suffix' field"
-+        assert 'globalLag' in first_csn, "CSN detail should contain 'globalLag' field"
-+        assert 'originServer' in first_csn, "CSN detail should contain 'originServer' field"
-+        assert 'arrivals' in first_csn, "CSN detail should contain 'arrivals' list"
-+        assert 'hops' in first_csn, "CSN detail should contain 'hops' list"
-+        assert isinstance(first_csn['arrivals'], list), "arrivals should be a list"
-+
-+        # Verify arrivals structure
-+        if first_csn['arrivals']:
-+            first_arrival = first_csn['arrivals'][0]
-+            assert 'server' in first_arrival, "Arrival should contain 'server' field"
-+            assert 'timestamp' in first_arrival, "Arrival should contain 'timestamp' field"
-+            assert 'relativeDelay' in first_arrival, "Arrival should contain 'relativeDelay' field"
-+
-+    # Verify csnId is included in datapoints for cross-reference
-+    if 'replicationLags' in json_data and json_data['replicationLags'].get('series'):
-+        for series in json_data['replicationLags']['series']:
-+            for datapoint in series['datapoints']:
-+                assert 'csnId' in datapoint, "Datapoint should contain 'csnId' for drill-down"
-+
-+    return csn_details
-+
-+
-+def _find_latest_logtime_for_prefix(log_dir, suffix, start_time, end_time, user_prefix):
-+    latest = None
-+    for fname in os.listdir(log_dir):
-+        if not fname.startswith('access'):
-+            continue
-+        full_path = os.path.join(log_dir, fname)
-+        parser = DSLogParser(
-+            logname=full_path,
-+            suffixes=[suffix],
-+            tz=timezone.utc,
-+            start_time=start_time,
-+            end_time=end_time
-+        )
-+        for record in parser.parse_file():
-+            target_dn = record.get('target_dn') or ''
-+            if user_prefix in target_dn:
-+                ts = record.get('timestamp')
-+                if ts and (latest is None or ts > latest):
-+                    latest = ts
-+    return latest
-+
-+
- def test_replication_log_monitoring_basic(topo_m4):
-     """Test basic replication log monitoring functionality
- 
-@@ -128,8 +223,7 @@ def test_replication_log_monitoring_basic(topo_m4):
- 
-     try:
-         # Clear logs and restart servers
--        for supplier in suppliers:
--            supplier.deleteAccessLogs(restart=True)
-+        _clear_access_logs(suppliers)
- 
-         # Generate test data with known patterns
-         log.info('Creating test data...')
-@@ -140,11 +234,10 @@ def test_replication_log_monitoring_basic(topo_m4):
-         repl.test_replication_topology(topo_m4)
- 
-         # Restart to flush logs
--        for supplier in suppliers:
--            supplier.restart()
-+        _restart_suppliers(suppliers)
- 
-         # Configure monitoring
--        log_dirs = [s.ds_paths.log_dir for s in suppliers]
-+        log_dirs = _get_log_dirs(suppliers)
-         repl_monitor = ReplicationLogAnalyzer(
-             log_dirs=log_dirs,
-             suffixes=[DEFAULT_SUFFIX],
-@@ -177,23 +270,23 @@ def test_replication_log_monitoring_basic(topo_m4):
-             assert DEFAULT_SUFFIX in csv_content
- 
-         # Verify PatternFly JSON content
--        with open(generated_files['json'], 'r') as f:
--            json_data = json.load(f)
--            assert 'replicationLags' in json_data
--            assert json_data['replicationLags']['series'], "Expected replication lag series in JSON output"
-+        json_data = _load_json(generated_files['json'])
-+        assert 'replicationLags' in json_data
-+        assert json_data['replicationLags']['series'], "Expected replication lag series in JSON output"
-+
-+        _assert_csn_details_schema(json_data)
- 
-         # Verify JSON summary
--        with open(generated_files['summary'], 'r') as f:
--            summary = json.load(f)
--            assert 'analysis_summary' in summary
--            stats = summary['analysis_summary']
-+        summary = _load_json(generated_files['summary'])
-+        assert 'analysis_summary' in summary
-+        stats = summary['analysis_summary']
- 
--            # Verify basic stats
--            assert stats['total_servers'] == len(suppliers)
--            assert stats['total_updates'] > 0
--            assert stats['updates_by_suffix'][DEFAULT_SUFFIX] > 0
--            assert 'average_lag' in stats
--            assert 'maximum_lag' in stats
-+        # Verify basic stats
-+        assert stats['total_servers'] == len(suppliers)
-+        assert stats['total_updates'] > 0
-+        assert stats['updates_by_suffix'][DEFAULT_SUFFIX] > 0
-+        assert 'average_lag' in stats
-+        assert 'maximum_lag' in stats
- 
-     finally:
-         _cleanup_test_data(test_users, tmp_dir)
-@@ -221,8 +314,7 @@ def test_replication_log_monitoring_advanced(topo_m4):
- 
-     try:
-         # Clear logs and restart servers
--        for supplier in suppliers:
--            supplier.deleteAccessLogs(restart=True)
-+        _clear_access_logs(suppliers)
- 
-         # Generate test data
-         start_time = datetime.now(timezone.utc)
-@@ -240,10 +332,9 @@ def test_replication_log_monitoring_advanced(topo_m4):
-         end_time = datetime.now(timezone.utc)
- 
-         # Restart to flush logs
--        for supplier in suppliers:
--            supplier.restart()
-+        _restart_suppliers(suppliers)
- 
--        log_dirs = [s.ds_paths.log_dir for s in suppliers]
-+        log_dirs = _get_log_dirs(suppliers)
- 
-         # Test 1: Lag time filtering
-         repl_monitor = ReplicationLogAnalyzer(
-@@ -374,8 +465,7 @@ def test_replication_log_monitoring_multi_suffix(topo_m4):
-             repl = ReplicationManager(suffix)
-             repl.test_replication_topology(topo_m4)
- 
--        for supplier in suppliers:
--            supplier.deleteAccessLogs(restart=True)
-+        _clear_access_logs(suppliers)
- 
-         start_time = datetime.now(timezone.utc)
- 
-@@ -400,11 +490,10 @@ def test_replication_log_monitoring_multi_suffix(topo_m4):
-         end_time = datetime.now(timezone.utc)
- 
-         # Restart to flush logs
--        for supplier in suppliers:
--            supplier.restart()
-+        _restart_suppliers(suppliers)
- 
-         # Monitor all suffixes
--        log_dirs = [s.ds_paths.log_dir for s in suppliers]
-+        log_dirs = _get_log_dirs(suppliers)
-         repl_monitor = ReplicationLogAnalyzer(
-             log_dirs=log_dirs,
-             suffixes=all_suffixes,
-@@ -466,8 +555,7 @@ def test_replication_log_monitoring_filter_combinations(topo_m4):
- 
-     try:
-         # Clear logs and restart servers
--        for supplier in suppliers:
--            supplier.deleteAccessLogs(restart=True)
-+        _clear_access_logs(suppliers)
- 
-         # Generate varied test data
-         start_time = datetime.now(timezone.utc)
-@@ -475,9 +563,7 @@ def test_replication_log_monitoring_filter_combinations(topo_m4):
- 
-         # Create different lag patterns
-         # Pause outbound agreements from supplier1 to build a replication backlog
--        for agmt in suppliers[0].agreement.list(suffix=DEFAULT_SUFFIX):
--            suppliers[0].agreement.pause(agmt.dn)
--            paused_agreements.append((suppliers[0], agmt.dn))
-+        paused_agreements = _pause_agreements(suppliers[0], DEFAULT_SUFFIX)
- 
-         for i, user in enumerate(test_users):
-             if i % 3 == 0:
-@@ -502,10 +588,9 @@ def test_replication_log_monitoring_filter_combinations(topo_m4):
-         end_time = datetime.now(timezone.utc)
- 
-         # Restart to flush logs
--        for supplier in suppliers:
--            supplier.restart()
-+        _restart_suppliers(suppliers)
- 
--        log_dirs = [s.ds_paths.log_dir for s in suppliers]
-+        log_dirs = _get_log_dirs(suppliers)
- 
-         # Test combined filters
-         lag_threshold = 0.5
-@@ -543,11 +628,321 @@ def test_replication_log_monitoring_filter_combinations(topo_m4):
-                 dt = datetime.fromtimestamp(t, timezone.utc)
-                 assert start_time <= dt <= end_time, "Time range filter violated"
-     finally:
--        for supplier_obj, dn in paused_agreements:
--            try:
--                supplier_obj.agreement.resume(dn)
--            except Exception as e:
--                log.warning(f"Failed to resume agreement {dn}: {e}")
-+        _resume_agreements(paused_agreements)
-+        _cleanup_test_data(test_users, tmp_dir)
-+
-+
-+def test_replication_log_monitoring_csn_details_edge_cases(topo_m4):
-+    """Test CSN details edge cases and structure validation
-+
-+    :id: f43dc473-4428-4971-be4c-169c4a78726e
-+    :setup: Four suppliers replication setup
-+    :steps:
-+        1. Test CSN details structure with various replication patterns
-+        2. Verify arrivals ordering and hop lag calculations
-+        3. Test partial replication scenarios
-+        4. Verify origin server detection
-+    :expectedresults:
-+        1. CSN details should have correct structure
-+        2. Arrivals should be ordered by timestamp
-+        3. Partial replication should be detected
-+        4. Origin server should be correctly identified
-+    """
-+    tmp_dir = tempfile.mkdtemp(prefix='repl_csn_edge_')
-+    test_users = []
-+    suppliers = [topo_m4.ms[f"supplier{i}"] for i in range(1, 5)]
-+
-+    try:
-+        _clear_access_logs(suppliers)
-+
-+        log.info('Creating test data for CSN details edge case testing...')
-+        test_users = _generate_test_data(suppliers[0], DEFAULT_SUFFIX, 5)
-+
-+        repl = ReplicationManager(DEFAULT_SUFFIX)
-+        repl.test_replication_topology(topo_m4)
-+
-+        _restart_suppliers(suppliers)
-+
-+        log_dirs = _get_log_dirs(suppliers)
-+        repl_monitor = ReplicationLogAnalyzer(
-+            log_dirs=log_dirs,
-+            suffixes=[DEFAULT_SUFFIX],
-+            anonymous=False,
-+            only_fully_replicated=True
-+        )
-+
-+        repl_monitor.parse_logs()
-+        generated_files = repl_monitor.generate_report(
-+            output_dir=tmp_dir,
-+            formats=['json'],
-+            report_name='csn_edge_test'
-+        )
-+
-+        assert os.path.exists(generated_files['json'])
-+
-+        json_data = _load_json(generated_files['json'])
-+
-+        assert 'csnDetails' in json_data, "csnDetails should be present"
-+        csn_details = json_data['csnDetails']
-+
-+        if csn_details:
-+            for csn, details in csn_details.items():
-+                arrivals = details.get('arrivals', [])
-+                if len(arrivals) > 1:
-+                    timestamps = [a['timestamp'] for a in arrivals]
-+                    assert timestamps == sorted(timestamps), \
-+                        f"Arrivals for CSN {csn} should be ordered by timestamp"
-+
-+                if arrivals:
-+                    origin_arrival = next((a for a in arrivals if a.get('isOrigin')), None)
-+                    assert origin_arrival is not None, "Expected an arrival marked as origin"
-+                    assert origin_arrival.get('server') == details.get('originServer'), \
-+                        "Origin arrival server should match originServer field"
-+
-+                    for i, arrival in enumerate(arrivals[1:], start=1):
-+                        assert 'hopLag' in arrival, \
-+                            f"Arrival {i} should have hopLag"
-+                        assert arrival['hopLag'] >= 0, \
-+                            "hopLag should be non-negative"
-+
-+                if len(arrivals) > 1:
-+                    global_lag = details.get('globalLag', 0)
-+                    assert global_lag >= 0, "globalLag should be non-negative"
-+
-+                    last_delay = arrivals[-1].get('relativeDelay', 0)
-+                    assert abs(last_delay - global_lag) < 0.001, \
-+                        "Last arrival's relativeDelay should match globalLag"
-+
-+                server_count = details.get('serverCount', 0)
-+                assert server_count == len(arrivals), \
-+                    "serverCount should match number of arrivals"
-+
-+                total_hops = details.get('totalHops', 0)
-+                expected_hops = max(0, len(arrivals) - 1)
-+                assert total_hops == expected_hops, \
-+                    f"totalHops should be {expected_hops}, got {total_hops}"
-+
-+                hops = details.get('hops', [])
-+                assert len(hops) == total_hops, \
-+                    "hops list length should match totalHops"
-+
-+            if 'replicationLags' in json_data and json_data['replicationLags'].get('series'):
-+                for series in json_data['replicationLags']['series']:
-+                    for datapoint in series.get('datapoints', []):
-+                        csn_id = datapoint.get('csnId')
-+                        if csn_id:
-+                            assert csn_id in csn_details, \
-+                                f"csnId {csn_id} in datapoint should exist in csnDetails"
-+
-+    finally:
-+        _cleanup_test_data(test_users, tmp_dir)
-+
-+
-+def test_replication_log_monitoring_origin_out_of_scope(topo_m4):
-+    """Test origin detection when origin server record is outside time range
-+
-+    :id: d73fd9c5-f930-47d6-ade4-ada1cf0d2c21
-+    :setup: Four suppliers replication setup
-+    :steps:
-+        1. Pause outbound agreements from the origin supplier
-+        2. Generate changes, then resume agreements to delay consumer arrivals
-+        3. Use time range that excludes the origin log but includes consumer logs
-+    :expectedresults:
-+        1. Origin server should be identified from replica ID mapping
-+        2. At least one CSN should have origin outside the selected time range
-+        3. JSON report should include csnDetails entries for validation
-+    """
-+    tmp_dir = tempfile.mkdtemp(prefix='repl_origin_scope_')
-+    test_users = []
-+    suppliers = [topo_m4.ms[f"supplier{i}"] for i in range(1, 5)]
-+    paused_agreements = []
-+
-+    try:
-+        # Reset access logs to make time-range cuts easier to reason about
-+        _clear_access_logs(suppliers)
-+
-+        # Pause outbound agreements so consumer logs won't see the pre-resume CSNs
-+        paused_agreements = _pause_agreements(suppliers[0], DEFAULT_SUFFIX)
-+
-+        log.info('Creating pre-resume changes for origin out-of-scope test...')
-+        # Create CSNs that originate on supplier1 but won't reach consumers yet
-+        pre_start = datetime.now(timezone.utc)
-+        test_users = _generate_test_data(
-+            suppliers[0], DEFAULT_SUFFIX, 2, user_prefix="origin_scope_pre"
-+        )
-+        pre_end = datetime.now(timezone.utc)
-+        # Locate the latest origin log time for these CSNs to set a deterministic cutoff
-+        origin_log_time = _find_latest_logtime_for_prefix(
-+            suppliers[0].ds_paths.log_dir,
-+            DEFAULT_SUFFIX,
-+            pre_start,
-+            pre_end,
-+            "origin_scope_pre_"
-+        )
-+        assert origin_log_time is not None, "Expected origin server log entries for pre-resume data"
-+        # Cut the analysis window just after origin logging, excluding supplier1 entries
-+        start_time = origin_log_time + timedelta(seconds=1)
-+        time.sleep(1)
-+
-+        # Resume agreements so the pre-resume CSNs replicate to consumers after start_time
-+        _resume_agreements(paused_agreements)
-+        paused_agreements.clear()
-+
-+        log.info('Creating post-resume changes for origin mapping...')
-+        # Additional CSNs after resume ensure normal replication continues
-+        test_users += _generate_test_data(
-+            suppliers[0], DEFAULT_SUFFIX, 2, user_prefix="origin_scope_post"
-+        )
-+
-+        # Wait for replication to finish and capture the upper bound of the time window
-+        repl = ReplicationManager(DEFAULT_SUFFIX)
-+        repl.test_replication_topology(topo_m4)
-+        end_time = datetime.now(timezone.utc)
-+
-+        # Restart to flush logs before analysis
-+        _restart_suppliers(suppliers)
-+
-+        log_dirs = _get_log_dirs(suppliers)
-+        repl_monitor = ReplicationLogAnalyzer(
-+            log_dirs=log_dirs,
-+            suffixes=[DEFAULT_SUFFIX],
-+            time_range={'start': start_time, 'end': end_time}
-+        )
-+
-+        # Parse logs within the time window and produce JSON details
-+        repl_monitor.parse_logs()
-+        generated_files = repl_monitor.generate_report(
-+            output_dir=tmp_dir,
-+            formats=['json'],
-+            report_name='origin_scope_test'
-+        )
-+
-+        json_data = _load_json(generated_files['json'])
-+
-+        csn_details = json_data.get('csnDetails', {})
-+        origin_server = suppliers[0].serverid
-+        found = False
-+        if csn_details:
-+            origin_counts = {}
-+            for details in csn_details.values():
-+                origin = details.get('originServer', 'unknown')
-+                origin_counts[origin] = origin_counts.get(origin, 0) + 1
-+            if origin_server not in origin_counts and f"slapd-{origin_server}" in origin_counts:
-+                origin_server = f"slapd-{origin_server}"
-+
-+        # Focus on the pre-resume CSNs; these should have origin out of scope
-+        pre_details = [
-+            details for details in csn_details.values()
-+            if "origin_scope_pre_" in (details.get('targetDn') or '')
-+        ]
-+        assert pre_details, "Expected pre-resume CSNs in csnDetails"
-+        # Confirm at least one CSN shows origin server missing from arrivals
-+        for details in pre_details:
-+            if details.get('originServer') != origin_server:
-+                continue
-+            arrivals = details.get('arrivals', [])
-+            arrival_servers = {a.get('server') for a in arrivals}
-+            if arrival_servers and origin_server not in arrival_servers:
-+                found = True
-+                break
-+            log.info(
-+                "Origin out-of-scope candidate: csn=%s arrivals=%s",
-+                details.get('csn'),
-+                sorted(arrival_servers)
-+            )
-+
-+        assert found, (
-+            "Expected at least one CSN where the origin server is outside the time range "
-+            "but still identified via replica ID mapping"
-+        )
-+
-+    finally:
-+        _resume_agreements(paused_agreements)
-+        _cleanup_test_data(test_users, tmp_dir)
-+
-+
-+def test_replication_log_monitoring_partial_replication(topo_m4):
-+    """Test CSN details with partial replication (not all servers reached)
-+
-+    :id: d4026fd0-d83b-400e-8c2e-44fcf676368f
-+    :setup: Four suppliers replication setup
-+    :steps:
-+        1. Pause replication agreements to create partial replication
-+        2. Generate changes and verify partial replication detection
-+        3. Verify replicatedToAll flag is correct
-+    :expectedresults:
-+        1. Partial replication should be detected
-+        2. replicatedToAll should be False for partially replicated CSNs
-+        3. serverCount should reflect actual servers reached
-+    """
-+    tmp_dir = tempfile.mkdtemp(prefix='repl_partial_')
-+    test_users = []
-+    suppliers = [topo_m4.ms[f"supplier{i}"] for i in range(1, 5)]
-+    paused_agreements = []
-+
-+    try:
-+        _clear_access_logs(suppliers)
-+
-+        log.info('Creating fully replicated test data...')
-+        test_users = _generate_test_data(suppliers[0], DEFAULT_SUFFIX, 3)
-+
-+        repl = ReplicationManager(DEFAULT_SUFFIX)
-+        repl.test_replication_topology(topo_m4)
-+
-+        _restart_suppliers(suppliers)
-+
-+        # Pause outbound agreements from supplier1 to create partial replication
-+        paused_agreements = _pause_agreements(suppliers[0], DEFAULT_SUFFIX)
-+
-+        log.info('Creating partially replicated test data...')
-+        test_users += _generate_test_data(suppliers[0], DEFAULT_SUFFIX, 3, user_prefix="partial_user")
-+
-+        # Allow some time for local logging; do not wait for full replication
-+        time.sleep(2)
-+
-+        log_dirs = _get_log_dirs(suppliers)
-+        repl_monitor = ReplicationLogAnalyzer(
-+            log_dirs=log_dirs,
-+            suffixes=[DEFAULT_SUFFIX],
-+            anonymous=False,
-+            only_fully_replicated=False,
-+            only_not_replicated=False
-+        )
-+
-+        repl_monitor.parse_logs()
-+        generated_files = repl_monitor.generate_report(
-+            output_dir=tmp_dir,
-+            formats=['json'],
-+            report_name='partial_repl_test'
-+        )
-+
-+        json_data = _load_json(generated_files['json'])
-+
-+        assert 'csnDetails' in json_data
-+        csn_details = json_data['csnDetails']
-+
-+        if csn_details:
-+            fully_replicated_count = sum(
-+                1 for details in csn_details.values()
-+                if details.get('replicatedToAll', False)
-+            )
-+
-+            assert fully_replicated_count > 0, \
-+                "Should have some fully replicated CSNs"
-+
-+            total_servers = len(suppliers)
-+            for csn, details in csn_details.items():
-+                server_count = details.get('serverCount', 0)
-+                assert server_count <= total_servers, \
-+                    f"serverCount ({server_count}) should not exceed total servers ({total_servers})"
-+
-+                replicated_to_all = details.get('replicatedToAll', False)
-+                if replicated_to_all:
-+                    assert server_count == total_servers, \
-+                        "replicatedToAll=True requires serverCount == total_servers"
-+
-+    finally:
-+        _resume_agreements(paused_agreements)
-         _cleanup_test_data(test_users, tmp_dir)
- 
- 
-diff --git a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
-index facbd9f5f..3c5a46a5b 100644
---- a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
-+++ b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
-@@ -3,17 +3,22 @@ import React from "react";
- import {
-     Button,
-     Checkbox,
-+    ClipboardCopy,
-+    ClipboardCopyVariant,
-     EmptyState,
-     EmptyStateIcon,
-     EmptyStateBody,
-     Grid,
-     GridItem,
-     Form,
-+    Label,
-     Modal,
-     ModalVariant,
-     NumberInput,
-     Radio,
-     Spinner,
-+    Split,
-+    SplitItem,
-     Tab,
-     Tabs,
-     TabTitleText,
-@@ -36,9 +41,12 @@ import {
-     ListItem
- } from "@patternfly/react-core";
- import {
-+    ArrowRightIcon,
-+    CheckCircleIcon,
-     CopyIcon,
-     OutlinedQuestionCircleIcon,
-     DownloadIcon,
-+    ServerIcon
- } from '@patternfly/react-icons';
- import PropTypes from "prop-types";
- import { get_date_string } from "../tools.jsx";
-@@ -71,6 +79,19 @@ const MAX_REPORT_JSON_SIZE = 64 * 1024 * 1024; // 64 MiB
- const MAX_BINARY_READ_SIZE = 64 * 1024 * 1024; // 64 MiB
- const CSV_PREVIEW_LINES = 20;
- 
-+const formatLagSeconds = (seconds, precision = 3) => {
-+    if (seconds === undefined || seconds === null) {
-+        return null;
-+    }
-+    if (seconds >= 3600) {
-+        return `${(seconds / 3600).toFixed(precision)}h`;
-+    }
-+    if (seconds >= 60) {
-+        return `${(seconds / 60).toFixed(precision)}m`;
-+    }
-+    return `${seconds.toFixed(precision)}s`;
-+};
-+
- class TaskLogModal extends React.Component {
-     render() {
-         const {
-@@ -1168,6 +1189,13 @@ class ScatterLineChart extends React.PureComponent {
-             }, 250);
-         };
-         this.toggleLegendItem = this.toggleLegendItem.bind(this);
-+        this.handlePointClick = this.handlePointClick.bind(this);
-+    }
-+
-+    handlePointClick(datum, seriesIndex) {
-+        if (this.props.onPointClick && datum.csnId) {
-+            this.props.onPointClick(datum);
-+        }
-     }
- 
-     componentDidMount() {
-@@ -1267,15 +1295,7 @@ class ScatterLineChart extends React.PureComponent {
-         const { series, yDomain } = this._getSeriesSnapshot();
- 
-         // Helper function to format time values
--        const formatTimeValue = (seconds) => {
--            if (seconds >= 3600) {
--                return `${(seconds / 3600).toFixed(3)}h`;
--            } else if (seconds >= 60) {
--                return `${(seconds / 60).toFixed(3)}m`;
--            } else {
--                return `${seconds.toFixed(3)}s`;
--            }
--        };
-+        const formatTimeValue = (seconds) => formatLagSeconds(seconds, 3);
- 
-         // Process tooltip HTML tags
-         const formatTooltip = (datum) => {
-@@ -1308,13 +1328,35 @@ class ScatterLineChart extends React.PureComponent {
-                                 labels={({ datum }) => formatTooltip(datum)}
-                                 constrainToVisibleArea
-                                 labelComponent={
--                                    <ChartTooltip
--                                        style={{
--                                            fontSize: "12px",
--                                            padding: 10,
--                                            whiteSpace: "pre-line" // Important for newlines
--                                        }}
--                                    />
-+                                <ChartTooltip
-+                                    orientation={({ datum }) => {
-+                                        // Position tooltip below for high points, above for low points
-+                                        // This prevents the tooltip from blocking clicks on points near the top
-+                                        const yMax = yDomain.max;
-+                                        const yMin = yDomain.min;
-+                                        const yRange = yMax - yMin;
-+                                        const threshold = yMin + (yRange * 0.6);
-+                                        return datum.y > threshold ? "bottom" : "top";
-+                                    }}
-+                                    style={{
-+                                        fontSize: "12px",
-+                                        padding: 10,
-+                                        whiteSpace: "pre-line", // Important for newlines
-+                                        pointerEvents: "none"
-+                                    }}
-+                                    flyoutStyle={{
-+                                        pointerEvents: "none"
-+                                    }}
-+                                    dx={0}
-+                                    dy={({ datum }) => {
-+                                        // Add extra offset to keep tooltip away from the point
-+                                        const yMax = yDomain.max;
-+                                        const yMin = yDomain.min;
-+                                        const yRange = yMax - yMin;
-+                                        const threshold = yMin + (yRange * 0.6);
-+                                        return datum.y > threshold ? 10 : -10;
-+                                    }}
-+                                />
-                                 }
-                             />
-                         }
-@@ -1410,6 +1452,7 @@ class ScatterLineChart extends React.PureComponent {
-                                 if (this.state.hiddenSeries[idx]) {
-                                     return null;
-                                 }
-+                                const hasClickHandler = !!this.props.onPointClick;
-                                 return (
-                                     <ChartScatter
-                                         key={`scatter-${idx}`}
-@@ -1417,9 +1460,39 @@ class ScatterLineChart extends React.PureComponent {
-                                         data={s.datapoints}
-                                         style={{
-                                             data: {
--                                                fill: s.color
-+                                                fill: s.color,
-+                                                cursor: hasClickHandler ? 'pointer' : 'default'
-                                             }
-                                         }}
-+                                        events={hasClickHandler ? [{
-+                                            target: "data",
-+                                            eventHandlers: {
-+                                                onClick: () => [{
-+                                                    target: "data",
-+                                                    mutation: (props) => {
-+                                                        this.handlePointClick(props.datum, idx);
-+                                                        return null;
-+                                                    }
-+                                                }],
-+                                                onMouseOver: () => [{
-+                                                    target: "data",
-+                                                    mutation: (props) => ({
-+                                                        style: {
-+                                                            ...props.style,
-+                                                            fill: s.color,
-+                                                            cursor: 'pointer',
-+                                                            strokeWidth: 2,
-+                                                            stroke: 'var(--pf-v5-global--active-color--100, #0066cc)',
-+                                                            r: 6
-+                                                        }
-+                                                    })
-+                                                }],
-+                                                onMouseOut: () => [{
-+                                                    target: "data",
-+                                                    mutation: () => null
-+                                                }]
-+                                            }
-+                                        }] : undefined}
-                                     />
-                                 );
-                             })}
-@@ -1501,17 +1574,320 @@ class ScatterLineChart extends React.PureComponent {
-     }
- }
- 
-+/**
-+ * CSNDetailModal - Displays detailed CSN propagation path information
-+ * Shows the hop-by-hop timing of how a change propagated through the replication topology
-+ */
-+class CSNDetailModal extends React.Component {
-+    constructor(props) {
-+        super(props);
-+        this.formatTimestamp = this.formatTimestamp.bind(this);
-+        this.formatLag = this.formatLag.bind(this);
-+    }
-+
-+    formatTimestamp(isoString) {
-+        if (!isoString) return _("Unknown");
-+        try {
-+            const date = new Date(isoString);
-+            if (isNaN(date.getTime())) {
-+                console.warn("Invalid timestamp format:", isoString);
-+                return cockpit.format(_("Invalid: $0"), isoString);
-+            }
-+            return date.toLocaleString(undefined, {
-+                year: 'numeric',
-+                month: '2-digit',
-+                day: '2-digit',
-+                hour: '2-digit',
-+                minute: '2-digit',
-+                second: '2-digit',
-+                fractionalSecondDigits: 3,
-+                hour12: false
-+            });
-+        } catch (e) {
-+            console.warn("Error formatting timestamp:", isoString, e);
-+            return cockpit.format(_("Invalid: $0"), isoString);
-+        }
-+    }
-+
-+    formatLag(seconds) {
-+        return formatLagSeconds(seconds, 3) || _("N/A");
-+    }
-+
-+    render() {
-+        const { csnData, onClose } = this.props;
-+
-+        if (!csnData) {
-+            return null;
-+        }
-+
-+        const arrivals = csnData.arrivals || [];
-+        const pathJson = JSON.stringify(csnData, null, 2);
-+
-+        return (
-+            <Modal
-+                variant={ModalVariant.large}
-+                title={_("CSN Propagation Details")}
-+                isOpen={!!csnData}
-+                onClose={onClose}
-+                aria-label={_("CSN propagation details")}
-+                actions={[
-+                    <Button key="close" variant="primary" onClick={onClose}>
-+                        {_("Close")}
-+                    </Button>
-+                ]}
-+            >
-+                        {/* CSN Summary Information */}
-+                        <Card isFlat className="ds-margin-bottom-md">
-+                            <CardBody>
-+                                <Grid hasGutter>
-+                                    <GridItem span={6}>
-+                                        <DescriptionList isHorizontal isCompact>
-+                                            <DescriptionListGroup>
-+                                                <DescriptionListTerm>{_("CSN")}</DescriptionListTerm>
-+                                                <DescriptionListDescription>
-+                                                    <ClipboardCopy
-+                                                        variant={ClipboardCopyVariant.inline}
-+                                                    >
-+                                                        {csnData.csn}
-+                                                    </ClipboardCopy>
-+                                                </DescriptionListDescription>
-+                                            </DescriptionListGroup>
-+                                            <DescriptionListGroup>
-+                                                <DescriptionListTerm>{_("Entry DN")}</DescriptionListTerm>
-+                                                <DescriptionListDescription>
-+                                                    <Tooltip content={csnData.targetDn}>
-+                                                        <span className="pf-v5-u-text-truncate" style={{ maxWidth: '300px', display: 'inline-block' }}>
-+                                                            {csnData.targetDn}
-+                                                        </span>
-+                                                    </Tooltip>
-+                                                </DescriptionListDescription>
-+                                            </DescriptionListGroup>
-+                                            <DescriptionListGroup>
-+                                                <DescriptionListTerm>{_("Suffix")}</DescriptionListTerm>
-+                                                <DescriptionListDescription>{csnData.suffix}</DescriptionListDescription>
-+                                            </DescriptionListGroup>
-+                                        </DescriptionList>
-+                                    </GridItem>
-+                                    <GridItem span={6}>
-+                                        <DescriptionList isHorizontal isCompact>
-+                                            <DescriptionListGroup>
-+                                                <DescriptionListTerm>{_("Origin Server")}</DescriptionListTerm>
-+                                                <DescriptionListDescription>
-+                                                    <Label color="blue" icon={<ServerIcon />}>
-+                                                        {csnData.originServer}
-+                                                    </Label>
-+                                                </DescriptionListDescription>
-+                                            </DescriptionListGroup>
-+                                            {csnData.originIncludedInArrivals === false && (
-+                                                <DescriptionListGroup>
-+                                                    <DescriptionListTerm>{_("Origin Note")}</DescriptionListTerm>
-+                                                    <DescriptionListDescription>
-+                                                        <Text component={TextVariants.small} style={{ color: 'var(--pf-v5-global--Color--200)' }}>
-+                                                            {_("Origin server record is outside the selected time range; entry details reflect the earliest arrival.")}
-+                                                        </Text>
-+                                                    </DescriptionListDescription>
-+                                                </DescriptionListGroup>
-+                                            )}
-+                                            <DescriptionListGroup>
-+                                                <DescriptionListTerm>{_("Total Lag")}</DescriptionListTerm>
-+                                                <DescriptionListDescription>
-+                                                    <strong>{this.formatLag(csnData.globalLag)}</strong>
-+                                                </DescriptionListDescription>
-+                                            </DescriptionListGroup>
-+                                            <DescriptionListGroup>
-+                                                <DescriptionListTerm>{_("Servers Reached")}</DescriptionListTerm>
-+                                                <DescriptionListDescription>
-+                                                    {csnData.serverCount}
-+                                                    {csnData.replicatedToAll && (
-+                                                        <Label color="green" icon={<CheckCircleIcon />} className="ds-left-margin">
-+                                                            {_("All")}
-+                                                        </Label>
-+                                                    )}
-+                                                </DescriptionListDescription>
-+                                            </DescriptionListGroup>
-+                                        </DescriptionList>
-+                                    </GridItem>
-+                                </Grid>
-+                            </CardBody>
-+                        </Card>
-+
-+                        {/* Arrival Timeline Visualization */}
-+                        <Card isFlat className="ds-margin-bottom-md">
-+                            <CardTitle>{_("Arrival Timeline")}</CardTitle>
-+                            <CardBody>
-+                                <Text
-+                                    component={TextVariants.small}
-+                                    className="ds-margin-bottom"
-+                                    style={{ color: 'var(--pf-v5-global--Color--200)', fontStyle: 'italic' }}
-+                                >
-+                                    {_("Note: Shows arrival order by time. Actual replication topology may differ in fan-out configurations.")}
-+                                </Text>
-+                                <div
-+                                    role="list"
-+                                    aria-label={_("CSN propagation timeline")}
-+                                    style={{
-+                                        display: 'flex',
-+                                        flexWrap: 'wrap',
-+                                        alignItems: 'center',
-+                                        gap: 'var(--pf-v5-global--spacer--sm)',
-+                                        padding: 'var(--pf-v5-global--spacer--sm)'
-+                                    }}
-+                                >
-+                                    {arrivals.map((arrival, idx) => (
-+                                        <React.Fragment key={idx}>
-+                                            {/* Server Node */}
-+                                            <div
-+                                                role="listitem"
-+                                                aria-label={cockpit.format(
-+                                                    arrival.isOrigin
-+                                                        ? _("Origin server: $0")
-+                                                        : _("Server $0, delay: $1"),
-+                                                    arrival.server,
-+                                                    arrival.isOrigin ? "" : this.formatLag(arrival.relativeDelay)
-+                                                )}
-+                                                style={{
-+                                                display: 'flex',
-+                                                flexDirection: 'column',
-+                                                alignItems: 'center',
-+                                                padding: 'var(--pf-v5-global--spacer--sm)',
-+                                                backgroundColor: arrival.isOrigin
-+                                                    ? 'var(--pf-v5-global--palette--blue-50, #e7f1fa)'
-+                                                    : 'var(--pf-v5-global--BackgroundColor--200, #f0f0f0)',
-+                                                borderRadius: 'var(--pf-v5-global--BorderRadius--sm)',
-+                                                border: arrival.isOrigin
-+                                                    ? '2px solid var(--pf-v5-global--primary-color--100, #0066cc)'
-+                                                    : '1px solid var(--pf-v5-global--BorderColor--100, #d2d2d2)',
-+                                                minWidth: '120px'
-+                                            }}>
-+                                                <Text component={TextVariants.small} style={{ fontWeight: 'bold' }}>
-+                                                    {arrival.server}
-+                                                </Text>
-+                                                <Text component={TextVariants.small} style={{ fontSize: '0.75rem', color: 'var(--pf-v5-global--Color--200)' }}>
-+                                                    {this.formatTimestamp(arrival.timestamp)}
-+                                                </Text>
-+                                                {arrival.isOrigin && (
-+                                                    <Label color="blue" isCompact style={{ marginTop: '4px' }}>
-+                                                        {_("Origin")}
-+                                                    </Label>
-+                                                )}
-+                                                {!arrival.isOrigin && (
-+                                                    <Text component={TextVariants.small} style={{ marginTop: '4px', color: 'var(--pf-v5-global--success-color--100)' }}>
-+                                                        +{this.formatLag(arrival.relativeDelay)}
-+                                                    </Text>
-+                                                )}
-+                                            </div>
-+
-+                                            {/* Arrow between nodes */}
-+                                            {idx < arrivals.length - 1 && (
-+                                                <div
-+                                                    role="presentation"
-+                                                    aria-hidden="true"
-+                                                    style={{
-+                                                        display: 'flex',
-+                                                        flexDirection: 'column',
-+                                                        alignItems: 'center',
-+                                                        padding: '0 var(--pf-v5-global--spacer--xs)'
-+                                                    }}
-+                                                >
-+                                                    <ArrowRightIcon style={{ color: 'var(--pf-v5-global--Color--200)' }} />
-+                                                    <Text component={TextVariants.small} style={{
-+                                                        fontSize: '0.7rem',
-+                                                        color: 'var(--pf-v5-global--Color--200)',
-+                                                        whiteSpace: 'nowrap'
-+                                                    }}>
-+                                                        {arrivals[idx + 1].hopLag !== undefined
-+                                                            ? this.formatLag(arrivals[idx + 1].hopLag)
-+                                                            : ''}
-+                                                    </Text>
-+                                                </div>
-+                                            )}
-+                                        </React.Fragment>
-+                                    ))}
-+                                </div>
-+                            </CardBody>
-+                        </Card>
-+
-+                        {/* Detailed Arrivals Table */}
-+                        <Card isFlat className="ds-margin-bottom-md">
-+                            <CardTitle>{_("Arrival Details")}</CardTitle>
-+                            <CardBody>
-+                                <table className="pf-v5-c-table pf-m-compact" role="grid">
-+                                    <thead>
-+                                        <tr>
-+                                            <th>{_("Server")}</th>
-+                                            <th>{_("Arrival Time")}</th>
-+                                            <th>{_("Hop Lag")}</th>
-+                                            <th>{_("Cumulative Delay")}</th>
-+                                            <th>{_("Duration")}</th>
-+                                        </tr>
-+                                    </thead>
-+                                    <tbody>
-+                                        {arrivals.map((arrival, idx) => (
-+                                            <tr key={idx}>
-+                                                <td>
-+                                                    {arrival.server}
-+                                                    {arrival.isOrigin && (
-+                                                        <span style={{ color: 'var(--pf-v5-global--primary-color--100)', marginLeft: 'var(--pf-v5-global--spacer--xs)' }}>
-+                                                            ({_("Origin")})
-+                                                        </span>
-+                                                    )}
-+                                                </td>
-+                                                <td>{this.formatTimestamp(arrival.timestamp)}</td>
-+                                                <td>
-+                                                    {arrival.isOrigin
-+                                                        ? <em>{_("Origin")}</em>
-+                                                        : this.formatLag(arrival.hopLag)}
-+                                                </td>
-+                                                <td>{this.formatLag(arrival.relativeDelay)}</td>
-+                                                <td>{arrival.duration ? this.formatLag(arrival.duration) : _("N/A")}</td>
-+                                            </tr>
-+                                        ))}
-+                                    </tbody>
-+                                </table>
-+                            </CardBody>
-+                        </Card>
-+
-+                        {/* Copy Actions */}
-+                        <Split hasGutter>
-+                            <SplitItem>
-+                                <ClipboardCopy
-+                                    variant={ClipboardCopyVariant.expansion}
-+                                    isExpanded={false}
-+                                    isCode
-+                                    isReadOnly
-+                                    hoverTip={_("Copy full path JSON")}
-+                                    clickTip={_("Copied!")}
-+                                >
-+                                    {pathJson}
-+                                </ClipboardCopy>
-+                            </SplitItem>
-+                        </Split>
-+            </Modal>
-+        );
-+    }
-+}
-+
-+CSNDetailModal.propTypes = {
-+    csnData: PropTypes.object,
-+    onClose: PropTypes.func.isRequired
-+};
-+
-+CSNDetailModal.defaultProps = {
-+    csnData: null
-+};
-+
- class LagReportModal extends React.Component {
-     constructor(props) {
-         super(props);
- 
-         this.state = {
--            activeTabKey: 0, // 0 = Summary, 1 = Charts, 2 = PNG Report, 3 = CSV Report, 4 = Report Files
-+            activeTabKey: 0,
-             ...this._freshReportState(),
-             loadingSummary: false,
-             loadingJson: false,
-             loadingCsv: false,
--            loadingPng: false
-+            loadingPng: false,
-+            selectedCsnId: null
-         };
- 
-         this.handleTabClick = this.handleTabClick.bind(this);
-@@ -1524,6 +1900,8 @@ class LagReportModal extends React.Component {
-         this.renderPngTab = this.renderPngTab.bind(this);
-         this.renderCsvTab = this.renderCsvTab.bind(this);
-         this.renderReportFilesTab = this.renderReportFilesTab.bind(this);
-+        this.handleCsnPointClick = this.handleCsnPointClick.bind(this);
-+        this.handleCloseCsnDetails = this.handleCloseCsnDetails.bind(this);
- 
-         this._activeLoadToken = 0;
-         this._isMounted = false;
-@@ -1543,10 +1921,26 @@ class LagReportModal extends React.Component {
-             summary: null,
-             suffixStats: {},
-             clientSamplingNotice: null,
-+            selectedCsnId: null,
-             ...overrides
-         };
-     }
- 
-+    handleCsnPointClick(datum) {
-+        if (datum && datum.csnId) {
-+            const { jsonData } = this.state;
-+            if (jsonData && jsonData.csnDetails && jsonData.csnDetails[datum.csnId]) {
-+                this.setState({ selectedCsnId: datum.csnId });
-+            } else {
-+                console.warn("CSN details not available for:", datum.csnId);
-+            }
-+        }
-+    }
-+
-+    handleCloseCsnDetails() {
-+        this.setState({ selectedCsnId: null });
-+    }
-+
-     componentDidMount() {
-         this._isMounted = true;
-         this.loadData();
-@@ -2117,7 +2511,7 @@ class LagReportModal extends React.Component {
- 
-     renderChartsTab() {
-         const { reportUrls } = this.props;
--        const { loadingJson, jsonData, error, clientSamplingNotice } = this.state;
-+        const { loadingJson, jsonData, error, clientSamplingNotice, selectedCsnId } = this.state;
- 
-         if (loadingJson) {
-             return (
-@@ -2170,6 +2564,13 @@ class LagReportModal extends React.Component {
-                           jsonData.hopLags.series &&
-                           jsonData.hopLags.series.length > 0;
- 
-+        const hasCsnDetails = jsonData && jsonData.csnDetails &&
-+                             Object.keys(jsonData.csnDetails).length > 0;
-+
-+        const selectedCsnData = selectedCsnId && hasCsnDetails
-+            ? jsonData.csnDetails[selectedCsnId]
-+            : null;
-+
-         if (!jsonData || (!hasReplicationLags && !hasHopLags)) {
-             return (
-                 <EmptyState>
-@@ -2201,6 +2602,15 @@ class LagReportModal extends React.Component {
-                                         {clientSamplingNotice}
-                                     </Alert>
-                                 )}
-+                                {hasCsnDetails && (
-+                                    <Text
-+                                        component={TextVariants.small}
-+                                        className="ds-margin-bottom"
-+                                        style={{ color: 'var(--pf-v5-global--Color--200)', fontStyle: 'italic' }}
-+                                    >
-+                                        {_("Tip: Click on any chart point to view detailed CSN propagation path.")}
-+                                    </Text>
-+                                )}
-                                 {hasReplicationLags && (
-                                     <div className="ds-margin-bottom">
-                                         <Title headingLevel="h3">
-@@ -2231,6 +2641,7 @@ class LagReportModal extends React.Component {
-                                             xAxisLabel={(jsonData.replicationLags.xAxisLabel || "").replace(/\s*Time\s*/g, "")}
-                                             yAxisLabel={jsonData.replicationLags.yAxisLabel || _("Lag Time (seconds)")}
-                                             defaultShowLegend={true}
-+                                            onPointClick={hasCsnDetails ? this.handleCsnPointClick : undefined}
-                                         />
-                                     </div>
-                                 )}
-@@ -2250,9 +2661,17 @@ class LagReportModal extends React.Component {
-                                             xAxisLabel={(jsonData.hopLags.xAxisLabel || "").replace(/\s*Time\s*/g, "")}
-                                             yAxisLabel={jsonData.hopLags.yAxisLabel || _("Hop Lag Time (seconds)")}
-                                             defaultShowLegend={false}
-+                                            onPointClick={hasCsnDetails ? this.handleCsnPointClick : undefined}
-                                         />
-                                     </div>
-                                 )}
-+
-+                                {/* CSN Detail Panel - shown when a point is clicked */}
-+                                <CSNDetailModal
-+                                    csnData={selectedCsnData}
-+                                    onClose={this.handleCloseCsnDetails}
-+                                />
-+
-                                 <div className="ds-margin-top">
-                                     <Button
-                                         variant="secondary"
-@@ -2876,6 +3295,28 @@ class ChooseLagReportModal extends React.Component {
- }
- 
- // Prototypes and defaultProps
-+ScatterLineChart.propTypes = {
-+    chartData: PropTypes.object,
-+    title: PropTypes.string,
-+    yAxisLabel: PropTypes.string,
-+    xAxisLabel: PropTypes.string,
-+    minY: PropTypes.number,
-+    maxY: PropTypes.number,
-+    defaultShowLegend: PropTypes.bool,
-+    onPointClick: PropTypes.func
-+};
-+
-+ScatterLineChart.defaultProps = {
-+    chartData: null,
-+    title: "",
-+    yAxisLabel: "Value",
-+    xAxisLabel: "",
-+    minY: null,
-+    maxY: null,
-+    defaultShowLegend: true,
-+    onPointClick: null
-+};
-+
- AgmtDetailsModal.propTypes = {
-     showModal: PropTypes.bool,
-     closeHandler: PropTypes.func,
-@@ -3044,6 +3485,7 @@ export {
-     ReportLoginModal,
-     FullReportContent,
-     LagReportModal,
--    ChooseLagReportModal
-+    ChooseLagReportModal,
-+    CSNDetailModal
- };
- 
-diff --git a/src/lib389/lib389/repltools.py b/src/lib389/lib389/repltools.py
-index 9d1aa4058..5ab8b3187 100644
---- a/src/lib389/lib389/repltools.py
-+++ b/src/lib389/lib389/repltools.py
-@@ -683,6 +683,7 @@ class ChartData(NamedTuple):
-     lags: List[float]
-     durations: List[float]
-     hover: List[str]
-+    csn_ids: List[str]
- 
- class VisualizationHelper:
-     """Helper class for visualization-related functionality."""
-@@ -730,7 +731,7 @@ class VisualizationHelper:
-                           tz: tzinfo = timezone.utc) -> Dict[Tuple[str, str], ChartData]:
-         """Prepare data for visualization with timezone-aware timestamps."""
-         chart_data = defaultdict(lambda: {
--            'times': [], 'lags': [], 'durations': [], 'hover': []
-+            'times': [], 'lags': [], 'durations': [], 'hover': [], 'csn_ids': []
-         })
- 
-         for csn, server_map in csns.items():
-@@ -766,6 +767,7 @@ class VisualizationHelper:
-                 data_slot['times'].append(ts_dt)
-                 data_slot['lags'].append(lag_val)  # The same global-lag for all servers
-                 data_slot['durations'].append(duration_val)
-+                data_slot['csn_ids'].append(csn)
-                 # Format timestamp for hover display in the specified timezone
-                 timestamp_str = ts_dt.strftime('%Y-%m-%d %H:%M:%S')
-                 data_slot['hover'].append(
-@@ -784,7 +786,8 @@ class VisualizationHelper:
-                 times=value['times'],
-                 lags=value['lags'],
-                 durations=value['durations'],
--                hover=value['hover']
-+                hover=value['hover'],
-+                csn_ids=value['csn_ids']
-             )
-             for key, value in chart_data.items()
-         }
-@@ -803,6 +806,14 @@ class ReplicationLogAnalyzer:
-     AUTO_SAMPLING_THRESHOLD = 4000  # Trigger auto sampling above this many CSN points
-     HOP_SERIES_BUDGET_RATIO = 0.25  # Allocate max 25% of chart points to hop lag series
-     MIN_POINTS_PER_SERIES = 2       # Minimum points to preserve series shape after sampling
-+    MAX_CSN_DETAILS = 10000         # Maximum CSN details to include (prevents memory issues)
-+
-+    # CSN format: TTTTTTTTSSSSRRRRNNNN (20 hex chars)
-+    # T=timestamp(8), S=sequence(4), R=replicaID(4), N=subseq(4)
-+    CSN_TIMESTAMP_START = 0
-+    CSN_TIMESTAMP_END = 8
-+    CSN_REPLICA_ID_START = 12
-+    CSN_REPLICA_ID_END = 16
- 
-     # Precision preset configurations
-     PRECISION_PRESETS = {
-@@ -867,6 +878,54 @@ class ReplicationLogAnalyzer:
-         # Threshold to trigger auto sampling if lots of CSNs
-         self._auto_sampling_csn_threshold = self.AUTO_SAMPLING_THRESHOLD
- 
-+        # Mapping of (replica ID, suffix) to server name
-+        # Built during log parsing to correctly identify origin servers
-+        # Keyed by (replica_id, suffix) because replica IDs are per-suffix in multi-suffix deployments
-+        self._replica_id_to_server: Dict[Tuple[str, str], str] = {}
-+
-+    @staticmethod
-+    def _extract_replica_id_from_csn(csn: str) -> Optional[str]:
-+        """Extract the replica ID from a CSN string.
-+
-+        CSN format: TTTTTTTTSSSSRRRRNNNN (20 hex characters)
-+        - T = timestamp (8 chars)
-+        - S = sequence (4 chars)
-+        - R = replica ID (4 chars)
-+        - N = subseq (4 chars)
-+
-+        :param csn: The CSN string
-+        :returns: The replica ID as a string (4 hex chars), or None if invalid
-+        """
-+        if not csn or not isinstance(csn, str) or len(csn) < 16:
-+            return None
-+        try:
-+            # Extract and validate replica ID is valid hex
-+            replica_id = csn[ReplicationLogAnalyzer.CSN_REPLICA_ID_START:
-+                            ReplicationLogAnalyzer.CSN_REPLICA_ID_END]
-+            int(replica_id, 16)  # Validate it's valid hex
-+            return replica_id
-+        except (ValueError, IndexError):
-+            return None
-+
-+    @staticmethod
-+    def _extract_timestamp_from_csn(csn: str) -> Optional[float]:
-+        """Extract the timestamp (epoch seconds) from a CSN string.
-+
-+        CSN format: TTTTTTTTSSSSRRRRNNNN (20 hex characters)
-+        - T = timestamp (8 chars, hex, seconds since epoch)
-+
-+        :param csn: The CSN string
-+        :returns: Timestamp as float (epoch seconds), or None if invalid
-+        """
-+        if not csn or not isinstance(csn, str) or len(csn) < 8:
-+            return None
-+        try:
-+            ts_hex = csn[ReplicationLogAnalyzer.CSN_TIMESTAMP_START:
-+                         ReplicationLogAnalyzer.CSN_TIMESTAMP_END]
-+            return float(int(ts_hex, 16))
-+        except (ValueError, IndexError):
-+            return None
-+
-     def _should_include_record(self, csn: str, server_map: Dict[Union[int, str], Dict[str, Any]]) -> bool:
-         """Determine if a record should be included based on filtering criteria."""
-         total_servers = self._active_server_count or len(self.log_dirs)
-@@ -990,6 +1049,148 @@ class ReplicationLogAnalyzer:
- 
-         return hops
- 
-+    def _build_csn_details(self, csn_whitelist: Optional[set] = None) -> Dict[str, Dict[str, Any]]:
-+        """Build detailed CSN propagation information for drill-down functionality.
-+
-+        Returns a dictionary keyed by CSN containing:
-+        - csn: The CSN string
-+        - targetDn: The target entry DN
-+        - suffix: The replication suffix
-+        - globalLag: Total propagation time (earliest to latest arrival)
-+        - originServer: The server where the change originated (determined by CSN replica ID)
-+        - originTime: ISO timestamp of origin
-+        - arrivals: Ordered list of server arrivals with timing details
-+        - hops: List of server-to-server hops with lag times
-+        - totalHops: Number of hops in the propagation path
-+        - replicatedToAll: Whether the change reached all servers
-+
-+        Note: Origin server is determined by the replica ID embedded in the CSN,
-+        not by earliest log timestamp (which can be incorrect under clock skew).
-+
-+        :param csn_whitelist: Optional set of CSN IDs to include. If provided,
-+                              only these CSNs will have details generated.
-+                              This prevents memory bloat when chart data is sampled.
-+        """
-+        csn_details = {}
-+        total_servers = self._active_server_count or len(self.log_dirs)
-+
-+        if csn_whitelist is not None:
-+            csn_items = [(csn, sm) for csn, sm in self.csns.items() if csn in csn_whitelist]
-+        else:
-+            csn_items = list(self.csns.items())
-+
-+        if len(csn_items) > self.MAX_CSN_DETAILS:
-+            self._logger.info(
-+                f"CSN details limited to {self.MAX_CSN_DETAILS} entries "
-+                f"(dataset has {len(csn_items)} CSNs). "
-+                "Selecting CSNs with highest global lag for drill-down."
-+            )
-+            csn_lags = []
-+            for csn, server_map in csn_items:
-+                t_list = [
-+                    rec.get('logtime', 0)
-+                    for key, rec in server_map.items()
-+                    if isinstance(rec, dict) and key != '__hop_lags__' and 'logtime' in rec
-+                ]
-+                if t_list:
-+                    lag = max(t_list) - min(t_list)
-+                    csn_lags.append((csn, server_map, lag))
-+            csn_lags.sort(key=lambda x: x[2], reverse=True)
-+            csn_items = [(csn, sm) for csn, sm, _ in csn_lags[:self.MAX_CSN_DETAILS]]
-+
-+        for csn, server_map in csn_items:
-+            valid_records = []
-+            for key, data in server_map.items():
-+                if isinstance(data, dict) and key != '__hop_lags__' and 'logtime' in data:
-+                    valid_records.append(data)
-+
-+            if not valid_records:
-+                continue
-+
-+            valid_records.sort(key=lambda x: x['logtime'])
-+
-+            suffix = None
-+            for rec in valid_records:
-+                if rec.get('suffix'):
-+                    suffix = rec['suffix']
-+                    break
-+
-+            replica_id = self._extract_replica_id_from_csn(csn)
-+            origin_server_name = None
-+            origin_record = None
-+
-+            origin_in_arrivals = False
-+            if replica_id and suffix:
-+                map_key = (replica_id, suffix)
-+                if map_key in self._replica_id_to_server:
-+                    origin_server_name = self._replica_id_to_server[map_key]
-+                    for rec in valid_records:
-+                        if rec.get('server_name') == origin_server_name:
-+                            origin_record = rec
-+                            origin_in_arrivals = True
-+                            break
-+
-+            if origin_record is None:
-+                origin_record = valid_records[0]
-+                if not origin_server_name:
-+                    origin_server_name = origin_record.get('server_name', 'unknown')
-+
-+            csn_ts = self._extract_timestamp_from_csn(csn)
-+            origin_time = csn_ts if csn_ts is not None else origin_record['logtime']
-+            earliest_time = valid_records[0]['logtime']
-+            latest_time = valid_records[-1]['logtime']
-+            global_lag = latest_time - earliest_time
-+
-+            arrivals = []
-+            for idx, rec in enumerate(valid_records):
-+                server_name = rec.get('server_name', 'unknown')
-+                is_origin = (server_name == origin_server_name)
-+
-+                arrival_entry = {
-+                    'server': server_name,
-+                    'timestamp': datetime.fromtimestamp(rec['logtime'], tz=self.tz).isoformat(),
-+                    'relativeDelay': rec['logtime'] - earliest_time,
-+                    'duration': float(rec.get('duration', 0.0)),
-+                    'etime': rec.get('etime')
-+                }
-+
-+                if is_origin:
-+                    arrival_entry['isOrigin'] = True
-+
-+                if idx > 0:
-+                    prev_rec = valid_records[idx - 1]
-+                    arrival_entry['hopFrom'] = prev_rec.get('server_name', 'unknown')
-+                    arrival_entry['hopLag'] = rec['logtime'] - prev_rec['logtime']
-+
-+                arrivals.append(arrival_entry)
-+
-+            hops = []
-+            for i in range(1, len(valid_records)):
-+                prev_rec = valid_records[i - 1]
-+                curr_rec = valid_records[i]
-+                hops.append({
-+                    'from': prev_rec.get('server_name', 'unknown'),
-+                    'to': curr_rec.get('server_name', 'unknown'),
-+                    'lag': curr_rec['logtime'] - prev_rec['logtime']
-+                })
-+
-+            csn_details[csn] = {
-+                'csn': csn,
-+                'targetDn': origin_record.get('target_dn', 'unknown') or 'unknown',
-+                'suffix': origin_record.get('suffix', 'unknown') or 'unknown',
-+                'globalLag': global_lag,
-+                'originServer': origin_server_name,
-+                'originIncludedInArrivals': origin_in_arrivals,
-+                'originTime': datetime.fromtimestamp(origin_time, tz=self.tz).isoformat(),
-+                'arrivals': arrivals,
-+                'hops': hops,
-+                'totalHops': len(hops),
-+                'serverCount': len(valid_records),
-+                'replicatedToAll': len(valid_records) == total_servers
-+            }
-+
-+        return csn_details
-+
-     def parse_logs(self) -> None:
-         """Parse logs from all directories. Each directory is treated as one server
-         unless anonymized, in which case we use 'server_{index}'.
-@@ -1044,6 +1245,41 @@ class ReplicationLogAnalyzer:
-                         'duration': record.get('duration', 0.0),
-                     }
- 
-+        # Build (replica ID, suffix) to server mapping based on closest CSN timestamp
-+        # Keyed by (replica_id, suffix) because replica IDs are per-suffix in multi-suffix deployments
-+        # For each (replica ID, suffix) pair, the server whose logtime is closest to the CSN
-+        # timestamp is the best origin candidate under clock skew.
-+        replica_id_best: Dict[Tuple[str, str], Tuple[bool, float, float, str]] = {}
-+        for csn, server_map in self.csns.items():
-+            replica_id = self._extract_replica_id_from_csn(csn)
-+            if not replica_id:
-+                continue
-+            csn_ts = self._extract_timestamp_from_csn(csn)
-+            for key, record in server_map.items():
-+                if not isinstance(record, dict) or key == '__hop_lags__':
-+                    continue
-+                logtime = record.get('logtime')
-+                server_name = record.get('server_name')
-+                suffix = record.get('suffix')
-+                if logtime is None or not server_name or not suffix:
-+                    continue
-+                # Prefer candidates where we can compare against CSN timestamp
-+                has_csn_ts = csn_ts is not None
-+                score = abs(logtime - csn_ts) if has_csn_ts else logtime
-+                map_key = (replica_id, suffix)
-+                if map_key not in replica_id_best:
-+                    replica_id_best[map_key] = (has_csn_ts, score, logtime, server_name)
-+                    continue
-+                prev_has_ts, prev_score, prev_logtime, _ = replica_id_best[map_key]
-+                if has_csn_ts and not prev_has_ts:
-+                    replica_id_best[map_key] = (has_csn_ts, score, logtime, server_name)
-+                elif has_csn_ts == prev_has_ts:
-+                    if score < prev_score or (score == prev_score and logtime < prev_logtime):
-+                        replica_id_best[map_key] = (has_csn_ts, score, logtime, server_name)
-+
-+        # Store the mapping ((replica ID, suffix) -> server name)
-+        self._replica_id_to_server = {k: srv for k, (_, _, _, srv) in replica_id_best.items()}
-+
-         # Apply filters after collecting all data
-         filtered_csns = {}
-         earliest_udt: Optional[float] = None
-@@ -1577,6 +1813,17 @@ class ReplicationLogAnalyzer:
-         except Exception as e:
-             raise IOError(f"Failed to write JSON summary to {outfile}: {e}")
- 
-+    @staticmethod
-+    def _collect_csn_ids(series_list: List[Dict[str, Any]]) -> set:
-+        """Collect CSN IDs from chart series datapoints."""
-+        csn_ids = set()
-+        for series in series_list:
-+            for dp in series.get("datapoints", []):
-+                csn_id = dp.get("csnId")
-+                if csn_id:
-+                    csn_ids.add(csn_id)
-+        return csn_ids
-+
-     def _generate_patternfly_json(self, results: Dict[str, Any], outfile: str) -> None:
-         """Generate JSON specifically formatted for PatternFly 5 charts."""
-         chart_data = VisualizationHelper.prepare_chart_data(self.csns, self.tz)
-@@ -1637,7 +1884,8 @@ class ReplicationLogAnalyzer:
-                 "x": data.times[i].isoformat(),
-                 "y": data.lags[i],
-                 "duration": data.durations[i],
--                "hoverInfo": data.hover[i]
-+                "hoverInfo": data.hover[i],
-+                "csnId": data.csn_ids[i]
-             } for i in indices]
-             series_data.append({
-                 "datapoints": datapoints,
-@@ -1645,7 +1893,7 @@ class ReplicationLogAnalyzer:
-                 "color": color_palette[idx % len(color_palette)]
-             })
- 
--        hop_data: Dict[str, Dict[str, List[Any]]] = defaultdict(lambda: {"times": [], "lags": [], "hover": []})
-+        hop_data: Dict[str, Dict[str, List[Any]]] = defaultdict(lambda: {"times": [], "lags": [], "hover": [], "csn_ids": []})
-         for csn, server_map in self.csns.items():
-             for hop in server_map.get('__hop_lags__', []):
-                 source = hop.get('supplier', 'unknown')
-@@ -1655,6 +1903,7 @@ class ReplicationLogAnalyzer:
-                 ts = datetime.fromtimestamp(hop.get('arrival_consumer', 0.0), tz=self.tz)
-                 entry["times"].append(ts)
-                 entry["lags"].append(hop.get('hop_lag', 0.0))
-+                entry["csn_ids"].append(csn)
-                 ts_str = ts.strftime('%Y-%m-%d %H:%M:%S')
-                 entry["hover"].append(
-                     f"Timestamp: {ts_str}<br>"
-@@ -1689,7 +1938,10 @@ class ReplicationLogAnalyzer:
-                 "name": key,
-                 "x": entry["times"][i].isoformat(),
-                 "y": entry["lags"][i],
--                "hoverInfo": entry["hover"][i].replace("Suffix: None", "Suffix: unknown").replace("Entry: None", "Entry: unknown")
-+                "hoverInfo": (entry["hover"][i]
-+                              .replace("Suffix: None", "Suffix: unknown")
-+                              .replace("Entry: None", "Entry: unknown")),
-+                "csnId": entry["csn_ids"][i]
-             } for i in indices]
-             hop_series.append({
-                 "datapoints": datapoints,
-@@ -1702,6 +1954,13 @@ class ReplicationLogAnalyzer:
-             reduced += sum(len(item["datapoints"]) for item in hop_series)
-             sampling_meta["reducedTotalPoints"] = reduced
- 
-+        sampled_csn_ids = None
-+        if sampling_meta["applied"]:
-+            sampled_csn_ids = self._collect_csn_ids(series_data)
-+            sampled_csn_ids.update(self._collect_csn_ids(hop_series))
-+
-+        csn_details = self._build_csn_details(csn_whitelist=sampled_csn_ids)
-+
-         pf_data = {
-             "replicationLags": {
-                 "title": "Global Replication Lag Over Time",
-@@ -1715,6 +1974,7 @@ class ReplicationLogAnalyzer:
-                 "xAxisLabel": "Time",
-                 "series": hop_series
-             },
-+            "csnDetails": csn_details,
-             "metadata": {
-                 "totalServers": self._active_server_count or len(self.log_dirs),
-                 "configuredLogDirs": self.log_dirs,
--- 
-2.52.0
-

diff --git a/0017-Issue-7224-CI-Test-Simplify-test_reserve_descriptor_.patch b/0017-Issue-7224-CI-Test-Simplify-test_reserve_descriptor_.patch
deleted file mode 100644
index 35a391e..0000000
--- a/0017-Issue-7224-CI-Test-Simplify-test_reserve_descriptor_.patch
+++ /dev/null
@@ -1,93 +0,0 @@
-From de48dd0da38fc715783e8efaada5af4c22298b0e Mon Sep 17 00:00:00 2001
-From: James Chapman <jachapma@redhat.com>
-Date: Thu, 5 Feb 2026 15:33:08 +0000
-Subject: [PATCH] Issue 7224 - CI Test - Simplify
- test_reserve_descriptor_validation (#7225)
-
-Description:
-Previously, the test_reserve_descriptor_validation CItest calculated
-the expected number of file descriptors based on backends, indexes,
-SSL/FIPS mode, and compared it to the value returned by the server.
-This approach is fragile, especially in FIPS mode.
-
-Fix:
-The test has been updated to simply verify that the server corrects
-the configured nsslapd-reservedescriptors value if it is set too low,
-instead of calculating the expected total.
-
-Fixes: https://github.com/389ds/389-ds-base/issues/7224
-
-Reviewed by: @bsimonova  (Thank you)
----
- .../suites/resource_limits/fdlimits_test.py   | 36 +++++++------------
- 1 file changed, 13 insertions(+), 23 deletions(-)
-
-diff --git a/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py b/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py
-index c843a4b24..a49e378c3 100644
---- a/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py
-+++ b/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py
-@@ -27,7 +27,7 @@ RESRV_FD_ATTR = "nsslapd-reservedescriptors"
- GLOBAL_LIMIT = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
- SYSTEMD_LIMIT = ensure_str(check_output("systemctl show -p LimitNOFILE dirsrv@standalone1".split(" ")).strip()).split('=')[1]
- CUSTOM_VAL = str(int(SYSTEMD_LIMIT) - 10)
--RESRV_DESC_VAL = str(10)
-+RESRV_DESC_VAL_LOW = 10
- TOO_HIGH_VAL = str(GLOBAL_LIMIT * 2)
- TOO_HIGH_VAL2 = str(int(SYSTEMD_LIMIT) * 2)
- TOO_LOW_VAL = "0"
-@@ -86,40 +86,30 @@ def test_reserve_descriptor_validation(topology_st):
-     :id: 9bacdbcc-7754-4955-8a56-1d8c82bce274
-     :setup: Standalone Instance
-     :steps:
--        1. Set attr nsslapd-reservedescriptors to a low value of RESRV_DESC_VAL (10)
-+        1. Set attr nsslapd-reservedescriptors to a low value (10)
-         2. Verify low value has been set
-         3. Restart instance (On restart the reservedescriptor attr will be validated)
--        4. Check updated value for nsslapd-reservedescriptors attr
-+        4. Verify corrected value for nsslapd-reservedescriptors > low value
-     :expectedresults:
-         1. Success
--        2. A value of RESRV_DESC_VAL (10) is returned
-+        2. A value of RESRV_DESC_VAL_LOW (10) is returned
-         3. Success
--        4. A value of STANDALONE_INST_RESRV_DESCS (55) is returned
-+        4. Corrected value for nsslapd-reservedescriptors > low value
-     """
- 
--    # Set nsslapd-reservedescriptors to a low value (RESRV_DESC_VAL:10)
--    topology_st.standalone.config.set(RESRV_FD_ATTR, RESRV_DESC_VAL)
--    resrv_fd = topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR)
--    assert resrv_fd == RESRV_DESC_VAL
-+    # Set nsslapd-reservedescriptors to a low value (10)
-+    topology_st.standalone.config.set(RESRV_FD_ATTR, str(RESRV_DESC_VAL_LOW))
-+    resrv_fd = int(topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR))
-+    assert resrv_fd == RESRV_DESC_VAL_LOW
- 
-     # An instance restart triggers a validation of the configured nsslapd-reservedescriptors attribute
-     topology_st.standalone.restart()
- 
--    """
--    A standalone instance contains a single backend with default indexes
--    so we only check these. TODO add tests for repl, chaining, PTA, SSL
--    """
--    STANDALONE_INST_RESRV_DESCS = 25 if is_fips() else 20 # Reserve descriptor constant (higher in FIPS mode)
--    backends = Backends(topology_st.standalone)
--    STANDALONE_INST_RESRV_DESCS += (len(backends.list()) * 4) # 4 = Backend descriptor constant
--    for be in backends.list() :
--        STANDALONE_INST_RESRV_DESCS += len(be.get_indexes().list())
--
--    # Varify reservedescriptors has been updated
--    resrv_fd = topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR)
--    assert resrv_fd == str(STANDALONE_INST_RESRV_DESCS)
-+    # Get the corrected value
-+    corrected_fd = int(topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR))
-+    assert corrected_fd > RESRV_DESC_VAL_LOW
- 
--    log.info("test_reserve_descriptor_validation PASSED")
-+    log.info(f"test_reserve_descriptor_validation PASSED (corrected from {RESRV_DESC_VAL_LOW} to {corrected_fd})")
- 
- @pytest.mark.skipif(ds_is_older("1.4.1.2"), reason="Not implemented")
- def test_reserve_descriptors_high(topology_st):
--- 
-2.52.0
-

diff --git a/0018-Issue-7223-Revert-index-scan-limits-for-system-index.patch b/0018-Issue-7223-Revert-index-scan-limits-for-system-index.patch
deleted file mode 100644
index 08c5af2..0000000
--- a/0018-Issue-7223-Revert-index-scan-limits-for-system-index.patch
+++ /dev/null
@@ -1,778 +0,0 @@
-From bdb2b85daa1358a182cf53bf68bbe019db5f82c3 Mon Sep 17 00:00:00 2001
-From: Viktor Ashirov <vashirov@redhat.com>
-Date: Thu, 5 Feb 2026 12:17:06 +0100
-Subject: [PATCH] Issue 7223 - Revert index scan limits for system indexes
-
-This reverts changes introduced by the following commits:
-c6f458b42 Issue 7189 - DSBLE0007 generates incorrect remediation commands for scan limits
-8b6b3a9f9 Issue 6966 - On large DB, unlimited IDL scan limit reduce the SRCH performance
-
-Relates: https://github.com/389ds/389-ds-base/issues/7223
-
-Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
----
- .../tests/suites/config/config_test.py        |  27 +---
- .../healthcheck/health_system_indexes_test.py | 136 +-----------------
- .../paged_results/paged_results_test.py       |  25 +---
- ldap/servers/slapd/back-ldbm/back-ldbm.h      |   1 -
- ldap/servers/slapd/back-ldbm/index.c          |   2 -
- ldap/servers/slapd/back-ldbm/instance.c       | 104 +++-----------
- ldap/servers/slapd/back-ldbm/ldbm_config.c    |  30 ----
- ldap/servers/slapd/back-ldbm/ldbm_config.h    |   1 -
- .../slapd/back-ldbm/ldbm_index_config.c       |   8 --
- src/lib389/lib389/backend.py                  |  50 ++-----
- src/lib389/lib389/cli_conf/backend.py         |  20 ---
- 11 files changed, 40 insertions(+), 364 deletions(-)
-
-diff --git a/dirsrvtests/tests/suites/config/config_test.py b/dirsrvtests/tests/suites/config/config_test.py
-index cbb8875fa..2c7d949d0 100644
---- a/dirsrvtests/tests/suites/config/config_test.py
-+++ b/dirsrvtests/tests/suites/config/config_test.py
-@@ -706,19 +706,17 @@ def test_ndn_cache_size_enforcement(topo, request):
- 
-     request.addfinalizer(fin)
- 
--def test_require_index(topo, request):
-+def test_require_index(topo):
-     """Validate that unindexed searches are rejected
- 
-     :id: fb6e31f2-acc2-4e75-a195-5c356faeb803
-     :setup: Standalone instance
-     :steps:
-         1. Set "nsslapd-require-index" to "on"
--        2. ancestorid/idlscanlimit to 100
--        3. Test an unindexed search is rejected
-+        2. Test an unindexed search is rejected
-     :expectedresults:
-         1. Success
-         2. Success
--        3. Success
-     """
- 
-     # Set the config
-@@ -729,10 +727,6 @@ def test_require_index(topo, request):
- 
-     db_cfg = DatabaseConfig(topo.standalone)
-     db_cfg.set([('nsslapd-idlistscanlimit', '100')])
--    backend = Backends(topo.standalone).get_backend(DEFAULT_SUFFIX)
--    ancestorid_index = backend.get_index('ancestorid')
--    ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND"))
--    topo.standalone.restart()
- 
-     users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
-     for i in range(101):
-@@ -743,15 +737,10 @@ def test_require_index(topo, request):
-     with pytest.raises(ldap.UNWILLING_TO_PERFORM):
-         raw_objects.filter("(description=test*)")
- 
--    def fin():
--        ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND"))
--
--    request.addfinalizer(fin)
--
- 
- 
- @pytest.mark.skipif(ds_is_older('1.4.2'), reason="The config setting only exists in 1.4.2 and higher")
--def test_require_internal_index(topo, request):
-+def test_require_internal_index(topo):
-     """Ensure internal operations require indexed attributes
- 
-     :id: 22b94f30-59e3-4f27-89a1-c4f4be036f7f
-@@ -782,10 +771,6 @@ def test_require_internal_index(topo, request):
-     # Create a bunch of users
-     db_cfg = DatabaseConfig(topo.standalone)
-     db_cfg.set([('nsslapd-idlistscanlimit', '100')])
--    backend = Backends(topo.standalone).get_backend(DEFAULT_SUFFIX)
--    ancestorid_index = backend.get_index('ancestorid')
--    ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND"))
--    topo.standalone.restart()
-     users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
-     for i in range(102, 202):
-         users.create_test_user(uid=i)
-@@ -810,12 +795,6 @@ def test_require_internal_index(topo, request):
-     with pytest.raises(ldap.UNWILLING_TO_PERFORM):
-         user.delete()
- 
--    def fin():
--        ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND"))
--
--    request.addfinalizer(fin)
--
--
- 
- def get_pstack(pid):
-     """Get a pstack of the pid."""
-diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-index 486fad44b..140845a33 100644
---- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-@@ -172,9 +172,7 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
- 
-     log.info("Re-add the parentId index")
-     backend = Backends(standalone).get("userRoot")
--    backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"],
--                      idlistscanlimit=['limit=5000 type=eq flags=AND'])
--    standalone.restart()
-+    backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
- 
-     run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
-     run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
-@@ -263,8 +261,7 @@ def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffer
- 
-     log.info("Re-add the entryusn index")
-     backend = Backends(standalone).get("userRoot")
--    backend.add_index("entryusn", ["eq"], matching_rules=["integerOrderingMatch"],
--                      idlistscanlimit=['limit=5000 type=eq flags=AND'])
-+    backend.add_index("entryusn", ["eq"], matching_rules=["integerOrderingMatch"])
- 
-     run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
-     run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
-@@ -408,132 +405,6 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable
-     run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
- 
- 
--def test_missing_scanlimit(topology_st, log_buffering_enabled):
--    """Check if healthcheck returns DSBLE0007 code when parentId index is missing scanlimit
--
--    :id: 40e1bf6a-2397-459b-bdf3-f787ca118b86
--    :setup: Standalone instance
--    :steps:
--        1. Create DS instance
--        2. Remove nsIndexIDListScanLimit from parentId index
--        3. Use healthcheck without --json option
--        4. Use healthcheck with --json option
--        5. Verify the remediation command has properly quoted scanlimit
--        6. Re-add the scanlimit
--        7. Use healthcheck without --json option
--        8. Use healthcheck with --json option
--    :expectedresults:
--        1. Success
--        2. Success
--        3. healthcheck reports DSBLE0007 code and related details
--        4. healthcheck reports DSBLE0007 code and related details
--        5. The scanlimit value is quoted in the remediation command
--        6. Success
--        7. healthcheck reports no issues found
--        8. healthcheck reports no issues found
--    """
--
--    RET_CODE = "DSBLE0007"
--    PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
--    SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
--
--    standalone = topology_st.standalone
--
--    log.info("Remove nsIndexIDListScanLimit from parentId index")
--    parentid_index = Index(standalone, PARENTID_DN)
--    parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
--
--    run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
--
--    # Verify the remediation command has properly quoted scanlimit
--    args = FakeArgs()
--    args.instance = standalone.serverid
--    args.verbose = standalone.verbose
--    args.list_errors = False
--    args.list_checks = False
--    args.exclude_check = []
--    args.check = ["backends"]
--    args.dry_run = False
--    args.json = False
--    health_check_run(standalone, topology_st.logcap.log, args)
--    # Check that the scanlimit is quoted in the output
--    assert topology_st.logcap.contains('--add-scanlimit "limit=5000 type=eq flags=AND"')
--    log.info("Verified scanlimit is properly quoted in remediation command")
--    topology_st.logcap.flush()
--
--    run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
--
--    log.info("Re-add the nsIndexIDListScanLimit")
--    parentid_index = Index(standalone, PARENTID_DN)
--    parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
--
--    run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
--    run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
--
--
--def test_missing_matching_rule_and_scanlimit(topology_st, log_buffering_enabled):
--    """Check if healthcheck generates a single combined command when both matching rule and scanlimit are missing
--
--    :id: af8214ad-5e4c-422a-8f74-3e99227551df
--    :setup: Standalone instance
--    :steps:
--        1. Create DS instance
--        2. Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index
--        3. Use healthcheck and verify a single combined command is generated
--        4. Re-add the matching rule and scanlimit
--        5. Use healthcheck without --json option
--        6. Use healthcheck with --json option
--    :expectedresults:
--        1. Success
--        2. Success
--        3. healthcheck reports DSBLE0007 and generates a single command with both --add-mr and --add-scanlimit
--        4. Success
--        5. healthcheck reports no issues found
--        6. healthcheck reports no issues found
--    """
--
--    RET_CODE = "DSBLE0007"
--    PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
--    SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
--
--    standalone = topology_st.standalone
--
--    log.info("Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index")
--    parentid_index = Index(standalone, PARENTID_DN)
--    parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
--    parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
--
--    # Run healthcheck and verify combined command
--    args = FakeArgs()
--    args.instance = standalone.serverid
--    args.verbose = standalone.verbose
--    args.list_errors = False
--    args.list_checks = False
--    args.exclude_check = []
--    args.check = ["backends"]
--    args.dry_run = False
--    args.json = False
--    health_check_run(standalone, topology_st.logcap.log, args)
--
--    # Verify DSBLE0007 is reported
--    assert topology_st.logcap.contains(RET_CODE)
--    log.info("healthcheck returned code: %s" % RET_CODE)
--
--    # Verify a single combined command is generated with both --add-mr and --add-scanlimit
--    assert topology_st.logcap.contains('--add-mr integerOrderingMatch --add-scanlimit "limit=5000 type=eq flags=AND"')
--    log.info("Verified combined command with both --add-mr and --add-scanlimit")
--
--    topology_st.logcap.flush()
--
--    log.info("Re-add the integerOrderingMatch matching rule and scanlimit")
--    parentid_index = Index(standalone, PARENTID_DN)
--    parentid_index.add("nsMatchingRule", "integerOrderingMatch")
--    parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
--
--    run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
--    run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
--
--
- def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
-     """Check if healthcheck returns DSBLE0007 code when multiple system indexes are missing
- 
-@@ -574,8 +445,7 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
- 
-     log.info("Re-add the missing system indexes")
-     backend = Backends(standalone).get("userRoot")
--    backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"],
--                      idlistscanlimit=['limit=5000 type=eq flags=AND'])
-+    backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
-     backend.add_index("nsuniqueid", ["eq"])
-     standalone.restart()
- 
-diff --git a/dirsrvtests/tests/suites/paged_results/paged_results_test.py b/dirsrvtests/tests/suites/paged_results/paged_results_test.py
-index 61d6702da..1bb94b53a 100644
---- a/dirsrvtests/tests/suites/paged_results/paged_results_test.py
-+++ b/dirsrvtests/tests/suites/paged_results/paged_results_test.py
-@@ -306,19 +306,19 @@ def test_search_success(topology_st, create_user, page_size, users_num):
-     del_users(users_list)
- 
- 
--@pytest.mark.parametrize("page_size,users_num,suffix,attr_name,attr_value,expected_err, restart", [
-+@pytest.mark.parametrize("page_size,users_num,suffix,attr_name,attr_value,expected_err", [
-     (50, 200, 'cn=config,%s' % DN_LDBM, 'nsslapd-idlistscanlimit', '100',
--     ldap.UNWILLING_TO_PERFORM, True),
-+     ldap.UNWILLING_TO_PERFORM),
-     (5, 15, DN_CONFIG, 'nsslapd-timelimit', '20',
--     ldap.UNAVAILABLE_CRITICAL_EXTENSION, False),
-+     ldap.UNAVAILABLE_CRITICAL_EXTENSION),
-     (21, 50, DN_CONFIG, 'nsslapd-sizelimit', '20',
--     ldap.SIZELIMIT_EXCEEDED, False),
-+     ldap.SIZELIMIT_EXCEEDED),
-     (21, 50, DN_CONFIG, 'nsslapd-pagedsizelimit', '5',
--     ldap.SIZELIMIT_EXCEEDED, False),
-+     ldap.SIZELIMIT_EXCEEDED),
-     (5, 50, 'cn=config,%s' % DN_LDBM, 'nsslapd-lookthroughlimit', '20',
--     ldap.ADMINLIMIT_EXCEEDED, False)])
-+     ldap.ADMINLIMIT_EXCEEDED)])
- def test_search_limits_fail(topology_st, create_user, page_size, users_num,
--                            suffix, attr_name, attr_value, expected_err, restart):
-+                            suffix, attr_name, attr_value, expected_err):
-     """Verify that search with a simple paged results control
-     throws expected exceptoins when corresponding limits are
-     exceeded.
-@@ -341,15 +341,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num,
- 
-     users_list = add_users(topology_st, users_num, DEFAULT_SUFFIX)
-     attr_value_bck = change_conf_attr(topology_st, suffix, attr_name, attr_value)
--    ancestorid_index = None
--    if attr_name == 'nsslapd-idlistscanlimit':
--        backend = Backends(topology_st.standalone).get_backend(DEFAULT_SUFFIX)
--        ancestorid_index = backend.get_index('ancestorid')
--        ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND"))
--
--    if (restart):
--        log.info('Instance restarted')
--        topology_st.standalone.restart()
-     conf_param_dict = {attr_name: attr_value}
-     search_flt = r'(uid=test*)'
-     searchreq_attrlist = ['dn', 'sn']
-@@ -402,8 +393,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num,
-             else:
-                 break
-     finally:
--        if ancestorid_index:
--            ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND"))
-         del_users(users_list)
-         change_conf_attr(topology_st, suffix, attr_name, attr_value_bck)
- 
-diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
-index b187c26bc..e23e7ff43 100644
---- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
-+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
-@@ -583,7 +583,6 @@ struct ldbminfo
-     int li_mode;
-     int li_lookthroughlimit;
-     int li_allidsthreshold;
--    int li_system_allidsthreshold;
-     char *li_directory;
-     int li_reslimit_lookthrough_handle;
-     uint64_t li_dbcachesize;
-diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c
-index 0ab82948c..a5004be19 100644
---- a/ldap/servers/slapd/back-ldbm/index.c
-+++ b/ldap/servers/slapd/back-ldbm/index.c
-@@ -997,8 +997,6 @@ index_read_ext_allids(
-     }
-     if (pb) {
-         slapi_pblock_get(pb, SLAPI_SEARCH_IS_AND, &is_and);
--    } else if (strcasecmp(type, LDBM_ANCESTORID_STR) == 0) {
--        is_and = 1;
-     }
-     ai_flags = is_and ? INDEX_ALLIDS_FLAG_AND : 0;
-     /* the caller can pass in a value of 0 - just ignore those - but if the index
-diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
-index 2a6e8cbb8..2b71cd4f7 100644
---- a/ldap/servers/slapd/back-ldbm/instance.c
-+++ b/ldap/servers/slapd/back-ldbm/instance.c
-@@ -16,7 +16,7 @@
- 
- /* Forward declarations */
- static void ldbm_instance_destructor(void **arg);
--Slapi_Entry *ldbm_instance_init_config_entry(char *cn_val, char *v1, char *v2, char *v3, char *v4, char *mr, char *scanlimit);
-+Slapi_Entry *ldbm_instance_init_config_entry(char *cn_val, char *v1, char *v2, char *v3, char *v4, char *mr);
- 
- 
- /* Creates and initializes a new ldbm_instance structure.
-@@ -126,7 +126,7 @@ done:
-  * Take a bunch of strings, and create a index config entry
-  */
- Slapi_Entry *
--ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3, char *val4, char *mr, char *scanlimit)
-+ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3, char *val4, char *mr)
- {
-     Slapi_Entry *e = slapi_entry_alloc();
-     struct berval *vals[2];
-@@ -167,11 +167,6 @@ ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3
-         slapi_entry_add_values(e, "nsMatchingRule", vals);
-     }
- 
--    if (scanlimit) {
--        val.bv_val = scanlimit;
--        val.bv_len = strlen(scanlimit);
--        slapi_entry_add_values(e, "nsIndexIDListScanLimit", vals);
--    }
-     return e;
- }
- 
-@@ -184,60 +179,8 @@ ldbm_instance_create_default_indexes(backend *be)
- {
-     Slapi_Entry *e;
-     ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
--    struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
-     /* write the dse file only on the final index */
-     int flags = LDBM_INSTANCE_CONFIG_DONT_WRITE;
--    char *ancestorid_indexes_limit = NULL;
--    char *parentid_indexes_limit = NULL;
--    struct attrinfo *ai = NULL;
--    int index_already_configured = 0;
--    struct index_idlistsizeinfo *iter;
--    int cookie;
--    int limit;
--
--    ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
--    if (ai && ai->ai_idlistinfo) {
--        iter = (struct index_idlistsizeinfo *)dl_get_first(ai->ai_idlistinfo, &cookie);
--        if (iter) {
--            limit = iter->ai_idlistsizelimit;
--            slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
--                      "set ancestorid limit to %d from attribute index\n",
--                      limit);
--        } else {
--            limit = li->li_system_allidsthreshold;
--            slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
--                      "set ancestorid limit to %d from default (fail to read limit)\n",
--                      limit);
--        }
--        ancestorid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", limit);
--    } else {
--        ancestorid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", li->li_system_allidsthreshold);
--        slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
--                      "set ancestorid limit to %d from default (no attribute or limit)\n",
--                      li->li_system_allidsthreshold);
--    }
--
--    ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
--    if (ai && ai->ai_idlistinfo) {
--        iter = (struct index_idlistsizeinfo *)dl_get_first(ai->ai_idlistinfo, &cookie);
--        if (iter) {
--            limit = iter->ai_idlistsizelimit;
--            slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
--                      "set parentid limit to %d from attribute index\n",
--                      limit);
--        } else {
--            limit = li->li_system_allidsthreshold;
--            slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
--                      "set parentid limit to %d from default (fail to read limit)\n",
--                      limit);
--        }
--        parentid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", limit);
--    } else {
--        parentid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", li->li_system_allidsthreshold);
--        slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
--                      "set parentid limit to %d from default (no attribute or limit)\n",
--                      li->li_system_allidsthreshold);
--    }
- 
-     /*
-      * Always index (entrydn or entryrdn), parentid, objectclass,
-@@ -245,48 +188,42 @@ ldbm_instance_create_default_indexes(backend *be)
-      * since they are used by some searches, replication and the
-      * ACL routines.
-      */
--    e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0, 0);
-+    e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0);
-     ldbm_instance_config_add_index_entry(inst, e, flags);
-     slapi_entry_free(e);
- 
--    ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
--    /* Check if the attrinfo is actually for parentid, not a fallback to .default */
--    index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_PARENTID_STR) == 0);
--    if (!index_already_configured) {
--        e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit);
--        ldbm_instance_config_add_index_entry(inst, e, flags);
--        attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
--        slapi_entry_free(e);
--    }
-+    e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
-+    ldbm_instance_config_add_index_entry(inst, e, flags);
-+    slapi_entry_free(e);
- 
--    e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0, 0);
-+    e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0);
-     ldbm_instance_config_add_index_entry(inst, e, flags);
-     slapi_entry_free(e);
- 
--    e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0, 0);
-+    e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0);
-     ldbm_instance_config_add_index_entry(inst, e, flags);
-     slapi_entry_free(e);
- 
--    e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0, 0);
-+    e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0);
-     ldbm_instance_config_add_index_entry(inst, e, flags);
-     slapi_entry_free(e);
- 
--    e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0, 0);
-+    e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0);
-     ldbm_instance_config_add_index_entry(inst, e, flags);
-     slapi_entry_free(e);
- 
-     /* For MMR, we need this attribute (to replace use of dncomp in delete). */
--    e = ldbm_instance_init_config_entry(ATTR_NSDS5_REPLCONFLICT, "eq", "pres", 0, 0, 0, 0);
-+    e = ldbm_instance_init_config_entry(ATTR_NSDS5_REPLCONFLICT, "eq", "pres", 0, 0, 0);
-     ldbm_instance_config_add_index_entry(inst, e, flags);
-     slapi_entry_free(e);
- 
-     /* write the dse file only on the final index */
--    e = ldbm_instance_init_config_entry(SLAPI_ATTR_NSCP_ENTRYDN, "eq", 0, 0, 0, 0, 0);
-+    e = ldbm_instance_init_config_entry(SLAPI_ATTR_NSCP_ENTRYDN, "eq", 0, 0, 0, 0);
-     ldbm_instance_config_add_index_entry(inst, e, flags);
-     slapi_entry_free(e);
- 
-     /* ldbm_instance_config_add_index_entry(inst, 2, argv); */
--    e = ldbm_instance_init_config_entry(LDBM_PSEUDO_ATTR_DEFAULT, "none", 0, 0, 0, 0, 0);
-+    e = ldbm_instance_init_config_entry(LDBM_PSEUDO_ATTR_DEFAULT, "none", 0, 0, 0, 0);
-     attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
-     slapi_entry_free(e);
- 
-@@ -294,18 +231,9 @@ ldbm_instance_create_default_indexes(backend *be)
-      * ancestorid is special, there is actually no such attr type
-      * but we still want to use the attr index file APIs.
-      */
--    ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
--    /* Check if the attrinfo is actually for ancestorid, not a fallback to .default */
--    index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_ANCESTORID_STR) == 0);
--    if (!index_already_configured) {
--        e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit);
--        ldbm_instance_config_add_index_entry(inst, e, flags);
--        attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
--        slapi_entry_free(e);
--    }
--
--    slapi_ch_free_string(&ancestorid_indexes_limit);
--    slapi_ch_free_string(&parentid_indexes_limit);
-+    e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
-+    attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
-+    slapi_entry_free(e);
- 
-     return 0;
- }
-diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.c b/ldap/servers/slapd/back-ldbm/ldbm_config.c
-index c24e3d766..6a2ce4c27 100644
---- a/ldap/servers/slapd/back-ldbm/ldbm_config.c
-+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.c
-@@ -385,35 +385,6 @@ ldbm_config_allidsthreshold_set(void *arg, void *value, char *errorbuf __attribu
-     return retval;
- }
- 
--static void *
--ldbm_config_system_allidsthreshold_get(void *arg)
--{
--    struct ldbminfo *li = (struct ldbminfo *)arg;
--
--    return (void *)((uintptr_t)(li->li_system_allidsthreshold));
--}
--
--static int
--ldbm_config_system_allidsthreshold_set(void *arg, void *value, char *errorbuf __attribute__((unused)), int phase __attribute__((unused)), int apply)
--{
--    struct ldbminfo *li = (struct ldbminfo *)arg;
--    int retval = LDAP_SUCCESS;
--    int val = (int)((uintptr_t)value);
--
--    /* Do whatever we can to make sure the data is ok. */
--
--    /* Catch attempts to configure a stupidly low ancestorid allidsthreshold */
--    if ((val > -1) && (val < 5000)) {
--        val = 5000;
--    }
--
--    if (apply) {
--        li->li_system_allidsthreshold = val;
--    }
--
--    return retval;
--}
--
- static void *
- ldbm_config_pagedallidsthreshold_get(void *arg)
- {
-@@ -1094,7 +1065,6 @@ static config_info ldbm_config[] = {
-     {CONFIG_LOOKTHROUGHLIMIT, CONFIG_TYPE_INT, "5000", &ldbm_config_lookthroughlimit_get, &ldbm_config_lookthroughlimit_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
-     {CONFIG_MODE, CONFIG_TYPE_INT_OCTAL, "0600", &ldbm_config_mode_get, &ldbm_config_mode_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
-     {CONFIG_IDLISTSCANLIMIT, CONFIG_TYPE_INT, "2147483646", &ldbm_config_allidsthreshold_get, &ldbm_config_allidsthreshold_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
--    {CONFIG_SYSTEMIDLISTSCANLIMIT, CONFIG_TYPE_INT, "5000", &ldbm_config_system_allidsthreshold_get, &ldbm_config_system_allidsthreshold_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
-     {CONFIG_DIRECTORY, CONFIG_TYPE_STRING, "", &ldbm_config_directory_get, &ldbm_config_directory_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE | CONFIG_FLAG_SKIP_DEFAULT_SETTING},
-     {CONFIG_MAXPASSBEFOREMERGE, CONFIG_TYPE_INT, "100", &ldbm_config_maxpassbeforemerge_get, &ldbm_config_maxpassbeforemerge_set, 0},
- 
-diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.h b/ldap/servers/slapd/back-ldbm/ldbm_config.h
-index 29a3426ab..e69bfeedf 100644
---- a/ldap/servers/slapd/back-ldbm/ldbm_config.h
-+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.h
-@@ -60,7 +60,6 @@ struct config_info
- #define CONFIG_RANGELOOKTHROUGHLIMIT "nsslapd-rangelookthroughlimit"
- #define CONFIG_PAGEDLOOKTHROUGHLIMIT "nsslapd-pagedlookthroughlimit"
- #define CONFIG_IDLISTSCANLIMIT "nsslapd-idlistscanlimit"
--#define CONFIG_SYSTEMIDLISTSCANLIMIT "nsslapd-systemidlistscanlimit"
- #define CONFIG_PAGEDIDLISTSCANLIMIT "nsslapd-pagedidlistscanlimit"
- #define CONFIG_DIRECTORY "nsslapd-directory"
- #define CONFIG_MODE "nsslapd-mode"
-diff --git a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
-index bae2a64b9..38e7368e1 100644
---- a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
-+++ b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
-@@ -384,14 +384,6 @@ ldbm_instance_config_add_index_entry(
-         }
-     }
- 
--    /* get nsIndexIDListScanLimit and its values, and add them */
--    if (0 == slapi_entry_attr_find(e, "nsIndexIDListScanLimit", &attr)) {
--        for (j = slapi_attr_first_value(attr, &sval); j != -1; j = slapi_attr_next_value(attr, j, &sval)) {
--            attrValue = slapi_value_get_berval(sval);
--            eBuf = PR_sprintf_append(eBuf, "nsIndexIDListScanLimit: %s\n", attrValue->bv_val);
--        }
--    }
--
-     ldbm_config_add_dse_entry(li, eBuf, flags);
-     if (eBuf) {
-         PR_smprintf_free(eBuf);
-diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
-index 274d45abe..f3dbe7c92 100644
---- a/src/lib389/lib389/backend.py
-+++ b/src/lib389/lib389/backend.py
-@@ -645,10 +645,11 @@ class Backend(DSLdapObject):
-         indexes = self.get_indexes()
- 
-         # Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
-+        # Note: entryrdn and ancestorid are internal system indexes that are not
-+        # exposed in cn=config - they are managed internally by the server.
-+        # Only parentid has a DSE config entry (for the integerOrderingMatch rule).
-         expected_system_indexes = {
--            'entryrdn': {'types': ['subtree'], 'matching_rule': None},
--            'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch', 'scanlimit': 'limit=5000 type=eq flags=AND'},
--            'ancestorid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch', 'scanlimit': 'limit=5000 type=eq flags=AND'},
-+            'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
-             'objectClass': {'types': ['eq'], 'matching_rule': None},
-             'aci': {'types': ['pres'], 'matching_rule': None},
-             'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
-@@ -705,17 +706,14 @@ class Backend(DSLdapObject):
-                     # Generate remediation command
-                     index_types = ' '.join([f"--index-type {t}" for t in expected_config['types']])
-                     cmd = f"dsconf YOUR_INSTANCE backend index add {bename} --attr {attr_name} {index_types}"
--                    if expected_config.get('matching_rule'):
-+                    if expected_config['matching_rule']:
-                         cmd += f" --matching-rule {expected_config['matching_rule']}"
--                    if expected_config.get('scanlimit'):
--                        cmd += f" --add-scanlimit \"{expected_config['scanlimit']}\""
-                     remediation_commands.append(cmd)
-                     reindex_attrs.add(attr_name)  # New index needs reindexing
-                 else:
-                     # Index exists, check configuration
-                     actual_types = index.get_attr_vals_utf8('nsIndexType') or []
-                     actual_mrs = index.get_attr_vals_utf8('nsMatchingRule') or []
--                    actual_scanlimit = index.get_attr_vals_utf8('nsIndexIDListScanLimit') or []
- 
-                     # Normalize to lowercase for comparison
-                     actual_types = [t.lower() for t in actual_types]
-@@ -730,31 +728,16 @@ class Backend(DSLdapObject):
-                         remediation_commands.append(cmd)
-                         reindex_attrs.add(attr_name)
- 
--                    # Check matching rules and scanlimit together to generate a single combined command
-+                    # Check matching rules
-                     expected_mr = expected_config.get('matching_rule')
--                    expected_scanlimit = expected_config.get('scanlimit')
--
--                    missing_mr = False
-                     if expected_mr:
-                         actual_mrs_lower = [mr.lower() for mr in actual_mrs]
-                         if expected_mr.lower() not in actual_mrs_lower:
-                             discrepancies.append(f"Index {attr_name} missing matching rule: {expected_mr}")
--                            missing_mr = True
--
--                    missing_scanlimit = False
--                    if expected_scanlimit and (len(actual_scanlimit) == 0):
--                        discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_scanlimit}")
--                        missing_scanlimit = True
--
--                    # Generate a single combined command for all missing items
--                    if missing_mr or missing_scanlimit:
--                        cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name}"
--                        if missing_mr:
--                            cmd += f" --add-mr {expected_mr}"
--                        if missing_scanlimit:
--                            cmd += f" --add-scanlimit \"{expected_scanlimit}\""
--                        remediation_commands.append(cmd)
--                        reindex_attrs.add(attr_name)
-+                            # Add the missing matching rule
-+                            cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr}"
-+                            remediation_commands.append(cmd)
-+                            reindex_attrs.add(attr_name)
- 
-             except Exception as e:
-                 self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}")
-@@ -993,13 +976,12 @@ class Backend(DSLdapObject):
-                 return
-         raise ValueError("Can not delete index because it does not exist")
- 
--    def add_index(self, attr_name, types, matching_rules=None, idlistscanlimit=None, reindex=False):
-+    def add_index(self, attr_name, types, matching_rules=None, reindex=False):
-         """ Add an index.
- 
-         :param attr_name - name of the attribute to index
-         :param types - a List of index types(eq, pres, sub, approx)
-         :param matching_rules - a List of matching rules for the index
--        :param idlistscanlimit - a List of fine grain definitions for scanning limit
-         :param reindex - If set to True then index the attribute after creating it.
-         """
- 
-@@ -1029,15 +1011,6 @@ class Backend(DSLdapObject):
-             # Only add if there are actually rules present in the list.
-             if len(mrs) > 0:
-                 props['nsMatchingRule'] = mrs
--
--        if idlistscanlimit is not None:
--            scanlimits = []
--            for scanlimit in idlistscanlimit:
--                scanlimits.append(scanlimit)
--            # Only add if there are actually limits in the list.
--            if len(scanlimits) > 0:
--                props['nsIndexIDListScanLimit'] = scanlimits
--
-         new_index.create(properties=props, basedn="cn=index," + self._dn)
- 
-         if reindex:
-@@ -1349,7 +1322,6 @@ class DatabaseConfig(DSLdapObject):
-             'nsslapd-lookthroughlimit',
-             'nsslapd-mode',
-             'nsslapd-idlistscanlimit',
--            'nsslapd-systemidlistscanlimit',
-             'nsslapd-directory',
-             'nsslapd-import-cachesize',
-             'nsslapd-idl-switch',
-diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
-index 9772e39d4..68efa795c 100644
---- a/src/lib389/lib389/cli_conf/backend.py
-+++ b/src/lib389/lib389/cli_conf/backend.py
-@@ -39,7 +39,6 @@ arg_to_attr = {
-         'mode': 'nsslapd-mode',
-         'state': 'nsslapd-state',
-         'idlistscanlimit': 'nsslapd-idlistscanlimit',
--        'systemidlistscanlimit': 'nsslapd-systemidlistscanlimit',
-         'directory': 'nsslapd-directory',
-         'dbcachesize': 'nsslapd-dbcachesize',
-         'logdirectory': 'nsslapd-db-logdirectory',
-@@ -626,21 +625,6 @@ def backend_set_index(inst, basedn, log, args):
-             except ldap.NO_SUCH_ATTRIBUTE:
-                 raise ValueError('Can not delete matching rule type because it does not exist')
- 
--    if args.replace_scanlimit is not None:
--        for replace_scanlimit in args.replace_scanlimit:
--            index.replace('nsIndexIDListScanLimit', replace_scanlimit)
--
--    if args.add_scanlimit is not None:
--        for add_scanlimit in args.add_scanlimit:
--            index.add('nsIndexIDListScanLimit', add_scanlimit)
--
--    if args.del_scanlimit is not None:
--        for del_scanlimit in args.del_scanlimit:
--            try:
--                index.remove('nsIndexIDListScanLimit', del_scanlimit)
--            except ldap.NO_SUCH_ATTRIBUTE:
--                raise ValueError('Can not delete a fine grain limit definition because it does not exist')
--
-     if args.reindex:
-         be.reindex(attrs=[args.attr])
-     log.info("Index successfully updated")
-@@ -963,9 +947,6 @@ def create_parser(subparsers):
-     edit_index_parser.add_argument('--del-type', action='append', help='Removes an index type from the index: (eq, sub, pres, or approx)')
-     edit_index_parser.add_argument('--add-mr', action='append', help='Adds a matching-rule to the index')
-     edit_index_parser.add_argument('--del-mr', action='append', help='Removes a matching-rule from the index')
--    edit_index_parser.add_argument('--add-scanlimit', action='append', help='Adds a fine grain limit definiton to the index')
--    edit_index_parser.add_argument('--replace-scanlimit', action='append', help='Replaces a fine grain limit definiton to the index')
--    edit_index_parser.add_argument('--del-scanlimit', action='append', help='Removes a fine grain limit definiton to the index')
-     edit_index_parser.add_argument('--reindex', action='store_true', help='Re-indexes the database after editing the index')
-     edit_index_parser.add_argument('be_name', help='The backend name or suffix')
- 
-@@ -1092,7 +1073,6 @@ def create_parser(subparsers):
-                                                                  'will check when examining candidate entries in response to a search request')
-     set_db_config_parser.add_argument('--mode', help='Specifies the permissions used for newly created index files')
-     set_db_config_parser.add_argument('--idlistscanlimit', help='Specifies the number of entry IDs that are searched during a search operation')
--    set_db_config_parser.add_argument('--systemidlistscanlimit', help='Specifies the number of entry IDs that are fetch from ancestorid/parentid indexes')
-     set_db_config_parser.add_argument('--directory', help='Specifies absolute path to database instance')
-     set_db_config_parser.add_argument('--dbcachesize', help='Specifies the database index cache size in bytes')
-     set_db_config_parser.add_argument('--logdirectory', help='Specifies the path to the directory that contains the database transaction logs')
--- 
-2.52.0
-

diff --git a/0019-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch b/0019-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch
deleted file mode 100644
index 2efb8f5..0000000
--- a/0019-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch
+++ /dev/null
@@ -1,212 +0,0 @@
-From ea1b51df9a698915b972923357caba1c78dcc82f Mon Sep 17 00:00:00 2001
-From: Viktor Ashirov <vashirov@redhat.com>
-Date: Thu, 5 Feb 2026 12:17:06 +0100
-Subject: [PATCH] Issue 7223 - Add upgrade function to remove
- nsIndexIDListScanLimit from parentid
-
-Description:
-Add `upgrade_remove_index_scanlimit()` function that removes the
-nsIndexIDListScanLimit attribute from parentid index configuration
-if present.
-
-This attribute was incorrectly added by a previous version and can
-cause issues with index configuration. The upgrade function runs
-automatically on server startup and removes the attribute if found.
-
-Relates: https://github.com/389ds/389-ds-base/issues/7223
-
-Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
----
- .../healthcheck/health_system_indexes_test.py |  52 +++++++++
- ldap/servers/slapd/upgrade.c                  | 105 ++++++++++++++++++
- 2 files changed, 157 insertions(+)
-
-diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-index 140845a33..aea88e0e2 100644
---- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-@@ -453,6 +453,58 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
-     run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
- 
- 
-+def test_upgrade_removes_parentid_scanlimit(topology_st):
-+    """Check if upgrade function removes nsIndexIDListScanLimit from parentid index
-+
-+    :id: 2808886e-c1c1-441d-b3a3-299c4ef1ab4a
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create DS instance
-+        2. Stop the server
-+        3. Use DSEldif to add nsIndexIDListScanLimit to parentid index
-+        4. Start the server (triggers upgrade)
-+        5. Verify nsIndexIDListScanLimit is removed from parentid index
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. Success
-+        4. Success
-+        5. nsIndexIDListScanLimit is no longer present
-+    """
-+    from lib389.dseldif import DSEldif
-+
-+    standalone = topology_st.standalone
-+    PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
-+    SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
-+
-+    log.info("Stop the server")
-+    standalone.stop()
-+
-+    log.info("Add nsIndexIDListScanLimit to parentid index using DSEldif")
-+    dse_ldif = DSEldif(standalone)
-+    dse_ldif.add(PARENTID_DN, "nsIndexIDListScanLimit", SCANLIMIT_VALUE)
-+
-+    # Verify it was added
-+    scanlimit = dse_ldif.get(PARENTID_DN, "nsIndexIDListScanLimit")
-+    assert scanlimit is not None, "Failed to add nsIndexIDListScanLimit"
-+    log.info(f"Added nsIndexIDListScanLimit: {scanlimit}")
-+
-+    log.info("Start the server (triggers upgrade)")
-+    standalone.start()
-+
-+    log.info("Verify nsIndexIDListScanLimit was removed by upgrade")
-+    # Check via LDAP - the upgrade should have removed it
-+    parentid_index = Index(standalone, PARENTID_DN)
-+    scanlimit_after = parentid_index.get_attr_vals_utf8("nsIndexIDListScanLimit")
-+    log.info(f"nsIndexIDListScanLimit after upgrade: {scanlimit_after}")
-+
-+    # The upgrade function should have removed nsIndexIDListScanLimit
-+    assert not scanlimit_after, \
-+        f"nsIndexIDListScanLimit should have been removed but found: {scanlimit_after}"
-+
-+    log.info("Upgrade successfully removed nsIndexIDListScanLimit from parentid index")
-+
-+
- if __name__ == "__main__":
-     # Run isolated
-     # -s for DEBUG mode
-diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
-index b02e37ed6..dcd16940b 100644
---- a/ldap/servers/slapd/upgrade.c
-+++ b/ldap/servers/slapd/upgrade.c
-@@ -330,6 +330,107 @@ upgrade_remove_subtree_rename(void)
-     return UPGRADE_SUCCESS;
- }
- 
-+/*
-+ * Remove nsIndexIDListScanLimit from parentid index configuration.
-+ *
-+ * This attribute was incorrectly added by a previous version and can
-+ * cause issues with index configuration. Remove it if present.
-+ */
-+static upgrade_status
-+upgrade_remove_index_scanlimit(void)
-+{
-+    struct slapi_pblock *pb = slapi_pblock_new();
-+    Slapi_Entry **backends = NULL;
-+    const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
-+    const char *be_filter = "(objectclass=nsBackendInstance)";
-+    const char *attrs_to_check[] = {"parentid", NULL};
-+    upgrade_status uresult = UPGRADE_SUCCESS;
-+
-+    /* Search for all backend instances */
-+    slapi_search_internal_set_pb(
-+            pb, be_base_dn,
-+            LDAP_SCOPE_ONELEVEL,
-+            be_filter, NULL, 0, NULL, NULL,
-+            plugin_get_default_component_id(), 0);
-+    slapi_search_internal_pb(pb);
-+    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
-+
-+    if (backends) {
-+        for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
-+            const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
-+            const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
-+            if (!be_dn || !be_name) {
-+                continue;
-+            }
-+
-+            for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
-+                const char *attr_name = attrs_to_check[attr_idx];
-+                struct slapi_pblock *idx_pb = slapi_pblock_new();
-+                Slapi_Entry **idx_entries = NULL;
-+                char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s",
-+                                                       attr_name, be_dn);
-+                char *idx_filter = "(objectclass=nsIndex)";
-+
-+                if (!idx_dn) {
-+                    slapi_pblock_destroy(idx_pb);
-+                    continue;
-+                }
-+
-+                slapi_search_internal_set_pb(
-+                        idx_pb, idx_dn,
-+                        LDAP_SCOPE_BASE,
-+                        idx_filter, NULL, 0, NULL, NULL,
-+                        plugin_get_default_component_id(), 0);
-+                slapi_search_internal_pb(idx_pb);
-+                slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
-+
-+                if (idx_entries && idx_entries[0]) {
-+                    /* Check if nsIndexIDListScanLimit is present */
-+                    if (slapi_entry_attr_get_ref(idx_entries[0], "nsIndexIDListScanLimit") != NULL) {
-+                        /* Remove nsIndexIDListScanLimit */
-+                        Slapi_PBlock *mod_pb = slapi_pblock_new();
-+                        Slapi_Mods smods;
-+                        int rc;
-+
-+                        slapi_mods_init(&smods, 1);
-+                        slapi_mods_add(&smods, LDAP_MOD_DELETE, "nsIndexIDListScanLimit", 0, NULL);
-+
-+                        slapi_modify_internal_set_pb(
-+                                mod_pb, idx_dn,
-+                                slapi_mods_get_ldapmods_byref(&smods),
-+                                NULL, NULL,
-+                                plugin_get_default_component_id(), 0);
-+                        slapi_modify_internal_pb(mod_pb);
-+                        slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
-+
-+                        if (rc == LDAP_SUCCESS) {
-+                            slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_index_scanlimit",
-+                                    "Removed 'nsIndexIDListScanLimit' from index '%s' in backend '%s'\n",
-+                                    attr_name, be_name);
-+                        } else if (rc != LDAP_NO_SUCH_ATTRIBUTE) {
-+                            slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_index_scanlimit",
-+                                    "Failed to remove 'nsIndexIDListScanLimit' from index '%s' in backend '%s': error %d\n",
-+                                    attr_name, be_name, rc);
-+                        }
-+
-+                        slapi_mods_done(&smods);
-+                        slapi_pblock_destroy(mod_pb);
-+                    }
-+                }
-+
-+                slapi_ch_free_string(&idx_dn);
-+                slapi_free_search_results_internal(idx_pb);
-+                slapi_pblock_destroy(idx_pb);
-+            }
-+        }
-+    }
-+
-+    slapi_free_search_results_internal(pb);
-+    slapi_pblock_destroy(pb);
-+
-+    return uresult;
-+}
-+
- /*
-  * Check if parentid/ancestorid indexes are missing the integerOrderingMatch
-  * matching rule.
-@@ -649,6 +750,10 @@ upgrade_server(void)
-         return UPGRADE_FAILURE;
-     }
- 
-+    if (upgrade_remove_index_scanlimit() != UPGRADE_SUCCESS) {
-+        return UPGRADE_FAILURE;
-+    }
-+
-     if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
-         return UPGRADE_FAILURE;
-     }
--- 
-2.52.0
-

diff --git a/0020-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch b/0020-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch
deleted file mode 100644
index 3f989d7..0000000
--- a/0020-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch
+++ /dev/null
@@ -1,313 +0,0 @@
-From b7ca2adc43db1be935101d6eb5dbf06566ea43ed Mon Sep 17 00:00:00 2001
-From: Viktor Ashirov <vashirov@redhat.com>
-Date: Thu, 5 Feb 2026 12:17:06 +0100
-Subject: [PATCH] Issue 7223 - Add upgrade function to remove ancestorid index
- config entry
-
-Description:
-Add `upgrade_remove_ancestorid_index_config()` function that removes:
-* ancestorid from `cn=default indexes`
-* ancestorid index config entries from each backend's `cn=index`
-
-Also remove ancestorid index configuration from template-dse.ldif.
-
-Relates: https://github.com/389ds/389-ds-base/issues/7223
-
-Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
----
- .../healthcheck/health_system_indexes_test.py |  85 +++++++++++
- ldap/ldif/template-dse.ldif.in                |   8 --
- ldap/servers/slapd/upgrade.c                  | 133 +++++++++++++++++-
- 3 files changed, 214 insertions(+), 12 deletions(-)
-
-diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-index aea88e0e2..eb727b902 100644
---- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-@@ -504,6 +504,91 @@ def test_upgrade_removes_parentid_scanlimit(topology_st):
- 
-     log.info("Upgrade successfully removed nsIndexIDListScanLimit from parentid index")
- 
-+    # Verify idempotency - restart again and ensure no errors
-+    log.info("Restart server again to verify idempotency (no errors on second run)")
-+    standalone.restart()
-+    # Verify the attribute is still absent
-+    scanlimit_after_second = parentid_index.get_attr_vals_utf8("nsIndexIDListScanLimit")
-+    assert not scanlimit_after_second, \
-+        f"nsIndexIDListScanLimit should still be absent after second restart but found: {scanlimit_after_second}"
-+    log.info("Idempotency verified - no issues on second restart")
-+
-+
-+def test_upgrade_removes_ancestorid_index_config(topology_st):
-+    """Check if upgrade function removes ancestorid index config entry
-+
-+    :id: 3f3d6e9b-75ac-4f0d-b2ce-7204e6eacd0a
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create DS instance
-+        2. Stop the server
-+        3. Use DSEldif to add an ancestorid index config entry
-+        4. Start the server (triggers upgrade)
-+        5. Verify ancestorid index config entry is removed
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. Success
-+        4. Success
-+        5. ancestorid index config entry is no longer present
-+    """
-+    from lib389.dseldif import DSEldif
-+
-+    standalone = topology_st.standalone
-+    ANCESTORID_DN = "cn=ancestorid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
-+
-+    log.info("Stop the server")
-+    standalone.stop()
-+
-+    log.info("Add ancestorid index config entry using DSEldif")
-+    dse_ldif = DSEldif(standalone)
-+
-+    # Create a fake ancestorid index entry
-+    ancestorid_entry = [
-+        "dn: {}\n".format(ANCESTORID_DN),
-+        "objectClass: top\n",
-+        "objectClass: nsIndex\n",
-+        "cn: ancestorid\n",
-+        "nsSystemIndex: true\n",
-+        "nsIndexType: eq\n",
-+        "nsMatchingRule: integerOrderingMatch\n",
-+        "\n"
-+    ]
-+    dse_ldif.add_entry(ancestorid_entry)
-+
-+    # Verify it was added by re-reading dse.ldif
-+    dse_ldif2 = DSEldif(standalone)
-+    cn_value = dse_ldif2.get(ANCESTORID_DN, "cn")
-+    assert cn_value is not None, "Failed to add ancestorid index config entry"
-+    log.info(f"Added ancestorid index entry with cn: {cn_value}")
-+
-+    log.info("Start the server (triggers upgrade)")
-+    standalone.start()
-+
-+    log.info("Verify ancestorid index config entry was removed by upgrade")
-+    # Check via LDAP - the upgrade should have removed the entry
-+    try:
-+        ancestorid_index = Index(standalone, ANCESTORID_DN)
-+        # If we can get the entry, it wasn't removed - this is a failure
-+        cn_after = ancestorid_index.get_attr_vals_utf8("cn")
-+        assert False, f"ancestorid index config entry should have been removed but still exists: {cn_after}"
-+    except Exception as e:
-+        # Entry should not exist - this is expected
-+        log.info(f"ancestorid index config entry correctly removed (got exception: {e})")
-+
-+    log.info("Upgrade successfully removed ancestorid index config entry")
-+
-+    # Verify idempotency - restart again and ensure no errors
-+    log.info("Restart server again to verify idempotency (no errors on second run)")
-+    standalone.restart()
-+    # Verify the entry is still absent
-+    try:
-+        ancestorid_index = Index(standalone, ANCESTORID_DN)
-+        cn_after_second = ancestorid_index.get_attr_vals_utf8("cn")
-+        assert False, f"ancestorid index config entry should still be absent after second restart but found: {cn_after_second}"
-+    except Exception as e:
-+        log.info(f"Idempotency verified - ancestorid still absent after second restart (got exception: {e})")
-+
- 
- if __name__ == "__main__":
-     # Run isolated
-diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in
-index bb8c71cd9..b6ab6f6c6 100644
---- a/ldap/ldif/template-dse.ldif.in
-+++ b/ldap/ldif/template-dse.ldif.in
-@@ -998,14 +998,6 @@ cn: aci
- nssystemindex: true
- nsindextype: pres
- 
--dn: cn=ancestorid,cn=default indexes, cn=config,cn=ldbm database,cn=plugins,cn=config
--objectclass: top
--objectclass: nsIndex
--cn: ancestorid
--nssystemindex: true
--nsindextype: eq
--nsmatchingrule: integerOrderingMatch
--
- dn: cn=cn,cn=default indexes, cn=config,cn=ldbm database,cn=plugins,cn=config
- objectclass: top
- objectclass: nsIndex
-diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
-index dcd16940b..6b1b012da 100644
---- a/ldap/servers/slapd/upgrade.c
-+++ b/ldap/servers/slapd/upgrade.c
-@@ -431,6 +431,126 @@ upgrade_remove_index_scanlimit(void)
-     return uresult;
- }
- 
-+/*
-+ * Remove ancestorid index configuration entry if present.
-+ *
-+ * The ancestorid index is special - it has no corresponding attribute type
-+ * and should not have a DSE config entry. If an entry exists, remove it.
-+ *
-+ * This function removes:
-+ * 1. The ancestorid entry from cn=default indexes (to prevent re-creation on startup)
-+ * 2. The ancestorid entry from each backend's cn=index (if it exists)
-+ */
-+static upgrade_status
-+upgrade_remove_ancestorid_index_config(void)
-+{
-+    struct slapi_pblock *pb = slapi_pblock_new();
-+    Slapi_Entry **backends = NULL;
-+    const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
-+    const char *be_filter = "(objectclass=nsBackendInstance)";
-+    upgrade_status uresult = UPGRADE_SUCCESS;
-+    int rc;
-+
-+    /*
-+     * First, remove ancestorid from cn=default indexes to prevent
-+     * ldbm_instance_create_default_user_indexes() from re-creating it.
-+     */
-+    {
-+        Slapi_PBlock *def_pb = slapi_pblock_new();
-+        char *def_idx_dn = slapi_create_dn_string(
-+                "cn=ancestorid,cn=default indexes,cn=config,%s", be_base_dn);
-+
-+        if (def_idx_dn) {
-+            slapi_delete_internal_set_pb(
-+                    def_pb, def_idx_dn, NULL, NULL,
-+                    plugin_get_default_component_id(), 0);
-+            slapi_delete_internal_pb(def_pb);
-+            slapi_pblock_get(def_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
-+
-+            if (rc == LDAP_SUCCESS) {
-+                slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_ancestorid_index_config",
-+                        "Removed 'ancestorid' from default indexes.\n");
-+            } else if (rc != LDAP_NO_SUCH_OBJECT) {
-+                slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_ancestorid_index_config",
-+                        "Failed to remove 'ancestorid' from default indexes: error %d\n", rc);
-+            }
-+
-+            slapi_ch_free_string(&def_idx_dn);
-+        }
-+        slapi_pblock_destroy(def_pb);
-+    }
-+
-+    /* Search for all backend instances */
-+    slapi_search_internal_set_pb(
-+            pb, be_base_dn,
-+            LDAP_SCOPE_ONELEVEL,
-+            be_filter, NULL, 0, NULL, NULL,
-+            plugin_get_default_component_id(), 0);
-+    slapi_search_internal_pb(pb);
-+    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
-+
-+    if (backends) {
-+        for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
-+            const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
-+            const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
-+            if (!be_dn || !be_name) {
-+                continue;
-+            }
-+
-+            struct slapi_pblock *idx_pb = slapi_pblock_new();
-+            Slapi_Entry **idx_entries = NULL;
-+            char *idx_dn = slapi_create_dn_string("cn=ancestorid,cn=index,%s",
-+                                                   be_dn);
-+            char *idx_filter = "(objectclass=nsIndex)";
-+
-+            if (!idx_dn) {
-+                slapi_pblock_destroy(idx_pb);
-+                continue;
-+            }
-+
-+            slapi_search_internal_set_pb(
-+                    idx_pb, idx_dn,
-+                    LDAP_SCOPE_BASE,
-+                    idx_filter, NULL, 0, NULL, NULL,
-+                    plugin_get_default_component_id(), 0);
-+            slapi_search_internal_pb(idx_pb);
-+            slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
-+
-+            if (idx_entries && idx_entries[0]) {
-+                /* ancestorid index entry exists - delete it */
-+                Slapi_PBlock *del_pb = slapi_pblock_new();
-+
-+                slapi_delete_internal_set_pb(
-+                        del_pb, idx_dn, NULL, NULL,
-+                        plugin_get_default_component_id(), 0);
-+                slapi_delete_internal_pb(del_pb);
-+                slapi_pblock_get(del_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
-+
-+                if (rc == LDAP_SUCCESS) {
-+                    slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_ancestorid_index_config",
-+                            "Removed 'ancestorid' index config entry in backend '%s'.\n",
-+                            be_name);
-+                } else if (rc != LDAP_NO_SUCH_OBJECT) {
-+                    slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_ancestorid_index_config",
-+                            "Failed to remove 'ancestorid' index config entry in backend '%s': error %d\n",
-+                            be_name, rc);
-+                }
-+
-+                slapi_pblock_destroy(del_pb);
-+            }
-+
-+            slapi_ch_free_string(&idx_dn);
-+            slapi_free_search_results_internal(idx_pb);
-+            slapi_pblock_destroy(idx_pb);
-+        }
-+    }
-+
-+    slapi_free_search_results_internal(pb);
-+    slapi_pblock_destroy(pb);
-+
-+    return uresult;
-+}
-+
- /*
-  * Check if parentid/ancestorid indexes are missing the integerOrderingMatch
-  * matching rule.
-@@ -445,7 +565,7 @@ upgrade_check_id_index_matching_rule(void)
-     Slapi_Entry **backends = NULL;
-     const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
-     const char *be_filter = "(objectclass=nsBackendInstance)";
--    const char *attrs_to_check[] = {"parentid", "ancestorid", NULL};
-+    const char *attrs_to_check[] = {"parentid", NULL};
-     upgrade_status uresult = UPGRADE_SUCCESS;
- 
-     /* Search for all backend instances */
-@@ -459,8 +579,9 @@ upgrade_check_id_index_matching_rule(void)
- 
-     if (backends) {
-         for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
-+            const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
-             const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
--            if (!be_name) {
-+            if (!be_dn || !be_name) {
-                 continue;
-             }
- 
-@@ -469,8 +590,8 @@ upgrade_check_id_index_matching_rule(void)
-                 const char *attr_name = attrs_to_check[attr_idx];
-                 struct slapi_pblock *idx_pb = slapi_pblock_new();
-                 Slapi_Entry **idx_entries = NULL;
--                char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,%s",
--                                                       attr_name, be_name, be_base_dn);
-+                char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s",
-+                                                       attr_name, be_dn);
-                 char *idx_filter = "(objectclass=nsIndex)";
-                 PRBool has_matching_rule = PR_FALSE;
- 
-@@ -754,6 +875,10 @@ upgrade_server(void)
-         return UPGRADE_FAILURE;
-     }
- 
-+    if (upgrade_remove_ancestorid_index_config() != UPGRADE_SUCCESS) {
-+        return UPGRADE_FAILURE;
-+    }
-+
-     if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
-         return UPGRADE_FAILURE;
-     }
--- 
-2.52.0
-

diff --git a/0021-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch b/0021-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch
deleted file mode 100644
index bff21ab..0000000
--- a/0021-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch
+++ /dev/null
@@ -1,300 +0,0 @@
-From 61d74ec426fa9404bb6ed49ca4fe644f69ef32f2 Mon Sep 17 00:00:00 2001
-From: Viktor Ashirov <vashirov@redhat.com>
-Date: Thu, 5 Feb 2026 12:17:06 +0100
-Subject: [PATCH] Issue 7223 - Detect and log index ordering mismatch during
- backend startup
-
-Description:
-Add `ldbm_instance_check_index_config()` function that checks on-disk
-index data and logs a message in case of a mismatch with DSE config entry.
-
-Relates: https://github.com/389ds/389-ds-base/issues/7223
-
-Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
----
- ldap/servers/slapd/back-ldbm/instance.c | 262 ++++++++++++++++++++++++
- 1 file changed, 262 insertions(+)
-
-diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
-index 2b71cd4f7..17bfc09a0 100644
---- a/ldap/servers/slapd/back-ldbm/instance.c
-+++ b/ldap/servers/slapd/back-ldbm/instance.c
-@@ -239,6 +239,266 @@ ldbm_instance_create_default_indexes(backend *be)
- }
- 
- 
-+/*
-+ * Check if an index has integerOrderingMatch configured in DSE.
-+ *
-+ * This function performs an internal LDAP search to check if the index
-+ * configuration entry has nsMatchingRule: integerOrderingMatch.
-+ *
-+ * Parameters:
-+ *   inst_name - backend instance name (e.g., "userRoot")
-+ *   index_name - name of the index to check (e.g., "parentid", "ancestorid")
-+ *
-+ * Returns:
-+ *   PR_TRUE if integerOrderingMatch is configured
-+ *   PR_FALSE if not configured or index entry doesn't exist
-+ */
-+static PRBool
-+ldbm_instance_index_has_int_order_in_dse(const char *inst_name, const char *index_name)
-+{
-+    Slapi_PBlock *pb = NULL;
-+    Slapi_Entry **entries = NULL;
-+    char *idx_dn = NULL;
-+    PRBool has_int_order = PR_FALSE;
-+
-+    idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,cn=ldbm database,cn=plugins,cn=config",
-+                                     index_name, inst_name);
-+    if (idx_dn == NULL) {
-+        return PR_FALSE;
-+    }
-+
-+    pb = slapi_pblock_new();
-+    slapi_search_internal_set_pb(pb, idx_dn, LDAP_SCOPE_BASE,
-+                                  "(objectclass=nsIndex)", NULL, 0, NULL, NULL,
-+                                  plugin_get_default_component_id(), 0);
-+    slapi_search_internal_pb(pb);
-+    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
-+
-+    if (entries && entries[0]) {
-+        Slapi_Attr *mr_attr = NULL;
-+        if (slapi_entry_attr_find(entries[0], "nsMatchingRule", &mr_attr) == 0) {
-+            Slapi_Value *sval = NULL;
-+            int idx;
-+            for (idx = slapi_attr_first_value(mr_attr, &sval);
-+                 idx != -1;
-+                 idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
-+                const struct berval *bval = slapi_value_get_berval(sval);
-+                if (bval && bval->bv_val &&
-+                    strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
-+                    has_int_order = PR_TRUE;
-+                    break;
-+                }
-+            }
-+        }
-+    }
-+
-+    slapi_ch_free_string(&idx_dn);
-+    slapi_free_search_results_internal(pb);
-+    slapi_pblock_destroy(pb);
-+
-+    return has_int_order;
-+}
-+
-+/*
-+ * Check a system index for ordering mismatch between config and on-disk data.
-+ *
-+ * This function compares what's configured in DSE (nsMatchingRule) with
-+ * what's actually on disk. A mismatch can occur in two scenarios:
-+ * 1. Ordering rule is configured but disk has lexicographic order
-+ *    (rule was added after index was created)
-+ * 2. No ordering rule configured but disk has integer order
-+ *    (rule was removed after index was created with it)
-+ *
-+ * This function reads the first keys from the specified index and checks
-+ * if they are stored in lexicographic order (string: "1" < "10" < "2") or
-+ * integer order (numeric: "1" < "2" < "10").
-+ *
-+ * Parameters:
-+ *   be - backend
-+ *   index_name - name of the index to check (e.g., "parentid", "ancestorid")
-+ *
-+ */
-+static void
-+ldbm_instance_check_index_config(backend *be, const char *index_name)
-+{
-+    ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
-+    struct attrinfo *ai = NULL;
-+    dbi_db_t *db = NULL;
-+    dbi_cursor_t dbc = {0};
-+    dbi_val_t key = {0};
-+    dbi_val_t data = {0};
-+    int ret = 0;
-+    PRBool config_has_int_order = PR_FALSE;
-+    PRBool disk_has_int_order = PR_TRUE;  /* Assume integer order until proven otherwise */
-+    ID prev_id = 0;
-+    int key_count = 0;
-+    PRBool first_key = PR_TRUE;
-+    PRBool found_ordering_evidence = PR_FALSE;
-+
-+    slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
-+            "Backend '%s': checking %s index ordering...\n",
-+            inst->inst_name, index_name);
-+
-+    /* Check if integerOrderingMatch is configured in DSE */
-+    config_has_int_order = ldbm_instance_index_has_int_order_in_dse(inst->inst_name, index_name);
-+
-+    /* Get attrinfo for the index */
-+    ainfo_get(be, (char *)index_name, &ai);
-+    if (ai == NULL || strcmp(ai->ai_type, index_name) != 0) {
-+        /* No index config found */
-+        slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
-+                "Backend '%s': no %s attrinfo found, skipping check\n",
-+                inst->inst_name, index_name);
-+        return;
-+    }
-+
-+    /* Open the index file */
-+    ret = dblayer_get_index_file(be, ai, &db, 0);
-+    if (ret != 0 || db == NULL) {
-+        /* Index file doesn't exist or can't be opened - this is fine for new instances */
-+        slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
-+                "Backend '%s': could not open %s index file (ret=%d), skipping order check\n",
-+                inst->inst_name, index_name, ret);
-+        return;
-+    }
-+
-+    /* Create a cursor to read keys */
-+    ret = dblayer_new_cursor(be, db, NULL, &dbc);
-+    if (ret != 0) {
-+        slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config",
-+                "Backend '%s': could not create cursor on %s index (ret=%d)\n",
-+                inst->inst_name, index_name, ret);
-+        dblayer_release_index_file(be, ai, db);
-+        return;
-+    }
-+
-+    dblayer_value_init(be, &key);
-+    dblayer_value_init(be, &data);
-+
-+    /*
-+     * Read up to 100 unique keys and check their ordering.
-+     * With lexicographic ordering: "1" < "10" < "100" < "2" < "20" < "3"
-+     * With integer ordering: "1" < "2" < "3" < "10" < "20" < "100"
-+     *
-+     * If we find a case where prev_id > current_id (numerically), but the
-+     * keys are still in order (lexicographically), then the index uses
-+     * lexicographic ordering.
-+     */
-+    while (key_count < 100) {
-+        ID current_id;
-+
-+        ret = dblayer_cursor_op(&dbc, first_key ? DBI_OP_MOVE_TO_FIRST : DBI_OP_NEXT_KEY, &key, &data);
-+        first_key = PR_FALSE;  /* Always advance cursor on next iteration */
-+        if (ret != 0) {
-+            break;  /* No more keys or error */
-+        }
-+
-+        /* Skip non-equality keys */
-+        if (key.size < 2 || *(char *)key.data != EQ_PREFIX) {
-+            continue;
-+        }
-+
-+        /* Parse the ID from the key (format: "=<id>") */
-+        current_id = (ID)strtoul((char *)key.data + 1, NULL, 10);
-+        if (current_id == 0) {
-+            continue;  /* Invalid ID, skip */
-+        }
-+
-+        key_count++;
-+
-+        if (prev_id != 0) {
-+            /*
-+             * Check ordering: if prev_id > current_id numerically,
-+             * but we got this key after prev in DB order, then
-+             * the index is using lexicographic ordering.
-+             *
-+             * Example: if we see "10" followed by "2", that's lexicographic
-+             * because "10" < "2" as strings, but 10 > 2 as integers.
-+             */
-+            if (prev_id > current_id) {
-+                /* Found evidence of lexicographic ordering */
-+                disk_has_int_order = PR_FALSE;
-+                found_ordering_evidence = PR_TRUE;
-+                break;
-+            } else if (prev_id < current_id) {
-+                /*
-+                 * This is consistent with integer ordering, but we need
-+                 * to find a case that proves lexicographic ordering.
-+                 * For example, seeing "1" followed by "2" is ambiguous,
-+                 * but seeing "1" followed by "10" (not "2") proves lexicographic.
-+                 *
-+                 * A definitive test: if we see an ID followed by a smaller
-+                 * ID, that's lexicographic. If all IDs are strictly increasing,
-+                 * it could be either (or the index only has sequential IDs).
-+                 */
-+                found_ordering_evidence = PR_TRUE;
-+            }
-+        }
-+        prev_id = current_id;
-+    }
-+
-+    /* Close the cursor and free values */
-+    dblayer_cursor_op(&dbc, DBI_OP_CLOSE, NULL, NULL);
-+    dblayer_value_free(be, &key);
-+    dblayer_value_free(be, &data);
-+
-+    /* Release the index file */
-+    dblayer_release_index_file(be, ai, db);
-+
-+    /*
-+     * Report findings and check for config/disk mismatch.
-+     * Log an error if there's a discrepancy between what's configured
-+     * in DSE and what's actually on disk.
-+     */
-+    if (!found_ordering_evidence) {
-+        slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
-+                "Backend '%s': %s index ordering check - "
-+                "could not determine on-disk ordering (index may be empty or have sequential IDs only). "
-+                "Config has integerOrderingMatch: %s\n",
-+                inst->inst_name, index_name, config_has_int_order ? "yes" : "no");
-+    } else if (config_has_int_order && !disk_has_int_order) {
-+        /* Config expects integer ordering, but disk has lexicographic - MISMATCH */
-+        slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config",
-+                "Backend '%s': MISMATCH - %s index has integerOrderingMatch configured, "
-+                "but on-disk data uses lexicographic ordering. "
-+                "This will cause searches to return incorrect or incomplete results. "
-+                "Please reindex the %s attribute: "
-+                "dsconf <instance> backend index reindex --attr %s %s\n",
-+                inst->inst_name, index_name, index_name, index_name, inst->inst_name);
-+    } else if (!config_has_int_order && disk_has_int_order) {
-+        /* Config expects lexicographic ordering, but disk has integer - MISMATCH */
-+        slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config",
-+                "Backend '%s': MISMATCH - %s index does not have integerOrderingMatch configured, "
-+                "but on-disk data uses integer ordering. "
-+                "This will cause searches to return incorrect or incomplete results. "
-+                "Please reindex the %s attribute: "
-+                "dsconf <instance> backend index reindex --attr %s %s\n",
-+                inst->inst_name, index_name, index_name, index_name, inst->inst_name);
-+    } else {
-+        /* Config and disk ordering match - no action needed */
-+        slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
-+                "Backend '%s': %s index ordering check passed - "
-+                "config has integerOrderingMatch: %s, on-disk data matches.\n",
-+                inst->inst_name, index_name, config_has_int_order ? "yes" : "no");
-+    }
-+}
-+
-+/*
-+ * Check system indexes for ordering mismatches.
-+ * If a mismatch is detected, log an error advising the administrator
-+ * to reindex the affected attribute.
-+ *
-+ * Note: We only check parentid here. The ancestorid index is a special
-+ * system index that has no DSE config entry - its ordering is hardcoded
-+ * in ldbm_instance_init_config_entry() and cannot be changed by users.
-+ */
-+static void
-+ldbm_instance_check_indexes(backend *be)
-+{
-+    /* Check parentid index */
-+    ldbm_instance_check_index_config(be, LDBM_PARENTID_STR);
-+}
-+
- /* Starts a backend instance */
- int
- ldbm_instance_start(backend *be)
-@@ -308,6 +568,8 @@ ldbm_instance_startall(struct ldbminfo *li)
-             ldbm_instance_register_modify_callback(inst);
-             vlv_init(inst);
-             slapi_mtn_be_started(inst->inst_be);
-+            /* Check index configuration for potential issues */
-+            ldbm_instance_check_indexes(inst->inst_be);
-         }
-         if (slapi_exist_referral(inst->inst_be)) {
-             slapi_be_set_flag(inst->inst_be, SLAPI_BE_FLAG_CONTAINS_REFERRAL);
--- 
-2.52.0
-

diff --git a/0022-Issue-7223-Add-dsctl-index-check-command-for-offline.patch b/0022-Issue-7223-Add-dsctl-index-check-command-for-offline.patch
deleted file mode 100644
index 818a9c7..0000000
--- a/0022-Issue-7223-Add-dsctl-index-check-command-for-offline.patch
+++ /dev/null
@@ -1,1233 +0,0 @@
-From d681f2e619caa2efddae83abc7d13860641a90cd Mon Sep 17 00:00:00 2001
-From: Viktor Ashirov <vashirov@redhat.com>
-Date: Thu, 5 Feb 2026 12:17:06 +0100
-Subject: [PATCH] Issue 7223 - Add dsctl index-check command for offline index
- repair
-
-Description:
-Add `dsctl <instance> index-check [backend] [--fix]` command for offline
-detection and repair of index ordering mismatches. This is needed after
-upgrade from versions that didn't use integerOrderingMatch for
-parentid/ancestorid system indexes.
-
-It's automatically executed as part of RPM %post scriptlet during
-upgrade.
-
-Relates: https://github.com/389ds/389-ds-base/issues/7223
-
-Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
----
- .../healthcheck/health_system_indexes_test.py | 593 ++++++++++++++++++
- rpm/389-ds-base.spec.in                       |  51 +-
- src/lib389/lib389/cli_ctl/dbtasks.py          | 402 ++++++++++++
- src/lib389/lib389/dseldif.py                  |  51 +-
- 4 files changed, 1068 insertions(+), 29 deletions(-)
-
-diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-index eb727b902..dd42cd197 100644
---- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-@@ -590,6 +590,599 @@ def test_upgrade_removes_ancestorid_index_config(topology_st):
-         log.info(f"Idempotency verified - ancestorid still absent after second restart (got exception: {e})")
- 
- 
-+def test_index_check_basic(topology_st):
-+    """Check if dsctl index-check works correctly
-+
-+    :id: 8a4e5c2d-1f3b-4a7c-9e8d-2b6f0c4a5d3e
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create DS instance
-+        2. Run dsctl index-check while server is running (should fail)
-+        3. Stop the server
-+        4. Run dsctl index-check (should pass)
-+        5. Start the server
-+    :expectedresults:
-+        1. Success
-+        2. index-check returns False and logs error
-+        3. Success
-+        4. index-check returns True (no mismatches)
-+        5. Success
-+    """
-+    from lib389.cli_ctl.dbtasks import dbtasks_index_check
-+
-+    standalone = topology_st.standalone
-+
-+    log.info("Run index-check while server is running")
-+    args = FakeArgs()
-+    args.backend = None
-+    args.fix = False
-+
-+    # Server should be running, index-check should fail
-+    assert standalone.status()
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is False
-+    assert topology_st.logcap.contains("index-check requires the instance to be stopped")
-+    topology_st.logcap.flush()
-+
-+    log.info("Stop the server")
-+    standalone.stop()
-+
-+    log.info("Run index-check with server stopped")
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is True
-+    assert topology_st.logcap.contains("All checks passed")
-+    topology_st.logcap.flush()
-+
-+    log.info("Start the server")
-+    standalone.start()
-+
-+
-+def test_index_check_specific_backend(topology_st):
-+    """Check if dsctl index-check works with a specific backend
-+
-+    :id: 407d8fcc-62e0-43dd-90fa-70e7090a5cfd
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create DS instance
-+        2. Stop the server
-+        3. Run dsctl index-check with specific backend (userRoot)
-+        4. Run dsctl index-check with non-existent backend
-+        5. Start the server
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. index-check returns True for userRoot
-+        4. index-check returns False for non-existent backend
-+        5. Success
-+    """
-+    from lib389.cli_ctl.dbtasks import dbtasks_index_check
-+
-+    standalone = topology_st.standalone
-+
-+    log.info("Stop the server")
-+    standalone.stop()
-+
-+    log.info("Run index-check for userRoot backend")
-+    args = FakeArgs()
-+    args.backend = "userRoot"
-+    args.fix = False
-+
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is True
-+    # Check for backend name in any case
-+    assert topology_st.logcap.contains("Checking backend:")
-+    topology_st.logcap.flush()
-+
-+    log.info("Run index-check for non-existent backend")
-+    args.backend = "nonExistentBackend"
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is False
-+    assert topology_st.logcap.contains("not found")
-+    topology_st.logcap.flush()
-+
-+    log.info("Start the server")
-+    standalone.start()
-+
-+
-+def test_index_check_mismatch_detection(topology_st):
-+    """Check if dsctl index-check detects ordering mismatch
-+
-+    :id: 50d14520-b0bf-4243-9fe6-b097928d4351
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create DS instance
-+        2. Stop the server
-+        3. Run dsctl index-check (without --fix)
-+        4. Verify output format
-+        5. Start the server
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. index-check returns True (no mismatch on fresh instance)
-+        4. Log contains expected format
-+        5. Success
-+    """
-+    from lib389.cli_ctl.dbtasks import dbtasks_index_check
-+
-+    standalone = topology_st.standalone
-+
-+    log.info("Stop the server")
-+    standalone.stop()
-+
-+    log.info("Run index-check to verify detection logic")
-+    args = FakeArgs()
-+    args.backend = "userRoot"
-+    args.fix = False
-+
-+    # On a fresh instance, there should be no mismatch
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    # Fresh instance should have matching config and disk ordering
-+    assert result is True
-+    # Check that the backend was checked (may skip indexes if ordering can't be determined)
-+    assert topology_st.logcap.contains("Checking backend:")
-+    topology_st.logcap.flush()
-+
-+    log.info("Start the server")
-+    standalone.start()
-+
-+
-+def test_index_check_with_fix(topology_st):
-+    """Check if dsctl index-check --fix triggers reindexing
-+
-+    :id: 38ae36e4-c861-4771-ae7d-354370376a2f
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create DS instance
-+        2. Stop the server
-+        3. Run dsctl index-check --fix (should pass since no mismatch)
-+        4. Verify output indicates check passed
-+        5. Start the server
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. index-check returns True
-+        4. Log contains "All checks passed"
-+        5. Success
-+    """
-+    from lib389.cli_ctl.dbtasks import dbtasks_index_check
-+
-+    standalone = topology_st.standalone
-+
-+    log.info("Stop the server")
-+    standalone.stop()
-+
-+    log.info("Run index-check with --fix option")
-+    args = FakeArgs()
-+    args.backend = None
-+    args.fix = True
-+
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    # On a fresh instance, there should be no mismatch, so no reindexing needed
-+    assert result is True
-+    assert topology_st.logcap.contains("All checks passed")
-+    topology_st.logcap.flush()
-+
-+    log.info("Start the server")
-+    standalone.start()
-+
-+
-+def test_index_check_fixes_scanlimit(topology_st):
-+    """Check if dsctl index-check --fix removes nsIndexIDListScanLimit
-+
-+    :id: 4a9b2c7d-8e1f-4b3a-9c5d-6e7f8a0b1c2d
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create DS instance
-+        2. Stop the server
-+        3. Add nsIndexIDListScanLimit to parentid index using DSEldif
-+        4. Run dsctl index-check (should detect issue)
-+        5. Run dsctl index-check --fix
-+        6. Verify nsIndexIDListScanLimit was removed
-+        7. Start the server
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. Success
-+        4. index-check returns False and detects scanlimit
-+        5. index-check returns True after fix
-+        6. nsIndexIDListScanLimit no longer present
-+        7. Success
-+    """
-+    from lib389.cli_ctl.dbtasks import dbtasks_index_check
-+    from lib389.dseldif import DSEldif
-+
-+    standalone = topology_st.standalone
-+
-+    log.info("Stop the server")
-+    standalone.stop()
-+
-+    log.info("Add nsIndexIDListScanLimit to parentid index using DSEldif")
-+    dse_ldif = DSEldif(standalone)
-+    parentid_dn = "cn=parentid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config"
-+    dse_ldif.add(parentid_dn, "nsIndexIDListScanLimit", "4000")
-+
-+    # Verify it was added
-+    scanlimit = dse_ldif.get(parentid_dn, "nsIndexIDListScanLimit", single=True)
-+    assert scanlimit == "4000", f"Failed to add nsIndexIDListScanLimit, got: {scanlimit}"
-+    log.info("Added nsIndexIDListScanLimit to parentid index")
-+
-+    log.info("Run index-check without --fix (should detect issue)")
-+    args = FakeArgs()
-+    args.backend = "userRoot"
-+    args.fix = False
-+
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is False, "index-check should detect scanlimit issue"
-+    assert topology_st.logcap.contains("nsIndexIDListScanLimit")
-+    topology_st.logcap.flush()
-+
-+    log.info("Run index-check with --fix")
-+    args.fix = True
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is True, "index-check --fix should succeed"
-+    assert topology_st.logcap.contains("Removed nsIndexIDListScanLimit")
-+    topology_st.logcap.flush()
-+
-+    log.info("Verify nsIndexIDListScanLimit was removed")
-+    dse_ldif = DSEldif(standalone)  # Reload to get fresh data
-+    scanlimit = dse_ldif.get(parentid_dn, "nsIndexIDListScanLimit", single=True)
-+    assert scanlimit is None, f"nsIndexIDListScanLimit should be removed, but got: {scanlimit}"
-+    log.info("nsIndexIDListScanLimit successfully removed")
-+
-+    log.info("Start the server")
-+    standalone.start()
-+
-+
-+def test_index_check_fixes_ancestorid_config(topology_st):
-+    """Check if dsctl index-check --fix removes ancestorid config entries
-+
-+    :id: 5b0c3d8e-9f2a-4c4b-0d6e-7f8a9b1c2d3e
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create DS instance
-+        2. Stop the server
-+        3. Add ancestorid index config entry using DSEldif
-+        4. Run dsctl index-check (should detect issue)
-+        5. Run dsctl index-check --fix
-+        6. Verify ancestorid config entry was removed
-+        7. Start the server
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. Success
-+        4. index-check returns False and detects ancestorid config
-+        5. index-check returns True after fix
-+        6. ancestorid config entry no longer present
-+        7. Success
-+    """
-+    from lib389.cli_ctl.dbtasks import dbtasks_index_check
-+    from lib389.dseldif import DSEldif
-+
-+    standalone = topology_st.standalone
-+
-+    log.info("Stop the server")
-+    standalone.stop()
-+
-+    log.info("Add ancestorid index config entry using DSEldif")
-+    dse_ldif = DSEldif(standalone)
-+    ancestorid_entry = [
-+        "dn: cn=ancestorid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config\n",
-+        "objectClass: top\n",
-+        "objectClass: nsIndex\n",
-+        "cn: ancestorid\n",
-+        "nsSystemIndex: true\n",
-+        "nsIndexType: eq\n",
-+    ]
-+    dse_ldif.add_entry(ancestorid_entry)
-+
-+    # Verify it was added
-+    ancestorid_dn = "cn=ancestorid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config"
-+    dse_ldif = DSEldif(standalone)  # Reload
-+    cn_value = dse_ldif.get(ancestorid_dn, "cn", single=True)
-+    assert cn_value is not None, "Failed to add ancestorid index config entry"
-+    log.info(f"Added ancestorid index entry with cn: {cn_value}")
-+
-+    log.info("Run index-check without --fix (should detect issue)")
-+    args = FakeArgs()
-+    args.backend = "userRoot"
-+    args.fix = False
-+
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is False, "index-check should detect ancestorid config issue"
-+    assert topology_st.logcap.contains("ancestorid") and topology_st.logcap.contains("config entry exists")
-+    topology_st.logcap.flush()
-+
-+    log.info("Run index-check with --fix")
-+    args.fix = True
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is True, "index-check --fix should succeed"
-+    assert topology_st.logcap.contains("Removed ancestorid config entry")
-+    topology_st.logcap.flush()
-+
-+    log.info("Verify ancestorid config entry was removed")
-+    dse_ldif = DSEldif(standalone)  # Reload to get fresh data
-+    cn_value = dse_ldif.get(ancestorid_dn, "cn", single=True)
-+    assert cn_value is None, f"ancestorid config entry should be removed, but got: {cn_value}"
-+    log.info("ancestorid config entry successfully removed")
-+
-+    log.info("Start the server")
-+    standalone.start()
-+
-+
-+def test_index_check_fixes_missing_matching_rule(topology_st):
-+    """Check if dsctl index-check --fix adds missing integerOrderingMatch
-+
-+    :id: 6c1d4e9f-0a3b-4d5c-1e7f-8a9b0c2d3e4f
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create DS instance
-+        2. Stop the server
-+        3. Remove integerOrderingMatch from parentid index using DSEldif
-+        4. Run dsctl index-check (should detect issue)
-+        5. Run dsctl index-check --fix
-+        6. Verify integerOrderingMatch was added back
-+        7. Start the server
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. Success
-+        4. index-check returns False and detects missing matching rule
-+        5. index-check returns True after fix
-+        6. integerOrderingMatch is present
-+        7. Success
-+    """
-+    from lib389.cli_ctl.dbtasks import dbtasks_index_check
-+    from lib389.dseldif import DSEldif
-+
-+    standalone = topology_st.standalone
-+
-+    log.info("Stop the server")
-+    standalone.stop()
-+
-+    log.info("Remove integerOrderingMatch from parentid index using DSEldif")
-+    dse_ldif = DSEldif(standalone)
-+    parentid_dn = "cn=parentid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config"
-+
-+    # Check current matching rules
-+    matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
-+    log.info(f"Current matching rules: {matching_rules}")
-+
-+    # Remove integerOrderingMatch if present
-+    if matching_rules:
-+        for mr in matching_rules:
-+            if "integerorderingmatch" in mr.lower():
-+                dse_ldif.delete(parentid_dn, "nsMatchingRule", mr)
-+                log.info(f"Removed matching rule: {mr}")
-+
-+    # Verify it was removed
-+    dse_ldif = DSEldif(standalone)  # Reload
-+    matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
-+    if matching_rules:
-+        for mr in matching_rules:
-+            assert "integerorderingmatch" not in mr.lower(), \
-+                f"integerOrderingMatch should be removed, but found: {mr}"
-+    log.info("integerOrderingMatch removed from parentid index")
-+
-+    log.info("Run index-check without --fix (should detect issue)")
-+    args = FakeArgs()
-+    args.backend = "userRoot"
-+    args.fix = False
-+
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is False, "index-check should detect missing matching rule"
-+    assert topology_st.logcap.contains("missing integerOrderingMatch")
-+    topology_st.logcap.flush()
-+
-+    log.info("Run index-check with --fix")
-+    args.fix = True
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is True, "index-check --fix should succeed"
-+    assert topology_st.logcap.contains("integerOrderingMatch")
-+    topology_st.logcap.flush()
-+
-+    log.info("Verify integerOrderingMatch was added back")
-+    dse_ldif = DSEldif(standalone)  # Reload to get fresh data
-+    matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
-+    assert matching_rules is not None, "nsMatchingRule should be present"
-+    found_int_order = False
-+    for mr in matching_rules:
-+        if "integerorderingmatch" in mr.lower():
-+            found_int_order = True
-+            break
-+    assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}"
-+    log.info("integerOrderingMatch successfully added back")
-+
-+    log.info("Start the server")
-+    standalone.start()
-+
-+
-+def test_index_check_fixes_default_ancestorid(topology_st):
-+    """Check if dsctl index-check --fix removes ancestorid from default indexes
-+
-+    :id: 7d2e5f0a-1b4c-4e6d-2f8a-9b0c1d3e4f5a
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create DS instance
-+        2. Stop the server
-+        3. Add ancestorid to cn=default indexes using DSEldif
-+        4. Run dsctl index-check (should detect issue)
-+        5. Run dsctl index-check --fix
-+        6. Verify ancestorid was removed from default indexes
-+        7. Start the server
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. Success
-+        4. index-check returns False and detects ancestorid in default indexes
-+        5. index-check returns True after fix
-+        6. ancestorid no longer in default indexes
-+        7. Success
-+    """
-+    from lib389.cli_ctl.dbtasks import dbtasks_index_check
-+    from lib389.dseldif import DSEldif
-+
-+    standalone = topology_st.standalone
-+
-+    log.info("Stop the server")
-+    standalone.stop()
-+
-+    log.info("Add ancestorid to cn=default indexes using DSEldif")
-+    dse_ldif = DSEldif(standalone)
-+    ancestorid_default_entry = [
-+        "dn: cn=ancestorid,cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config\n",
-+        "objectClass: top\n",
-+        "objectClass: nsIndex\n",
-+        "cn: ancestorid\n",
-+        "nsSystemIndex: true\n",
-+        "nsIndexType: eq\n",
-+    ]
-+    dse_ldif.add_entry(ancestorid_default_entry)
-+
-+    # Verify it was added
-+    ancestorid_default_dn = "cn=ancestorid,cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config"
-+    dse_ldif = DSEldif(standalone)  # Reload
-+    cn_value = dse_ldif.get(ancestorid_default_dn, "cn", single=True)
-+    assert cn_value is not None, "Failed to add ancestorid to default indexes"
-+    log.info(f"Added ancestorid to default indexes with cn: {cn_value}")
-+
-+    log.info("Run index-check without --fix (should detect issue)")
-+    args = FakeArgs()
-+    args.backend = None  # Check all backends including default indexes
-+    args.fix = False
-+
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is False, "index-check should detect ancestorid in default indexes"
-+    assert topology_st.logcap.contains("ancestorid found in cn=default indexes")
-+    topology_st.logcap.flush()
-+
-+    log.info("Run index-check with --fix")
-+    args.fix = True
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is True, "index-check --fix should succeed"
-+    assert topology_st.logcap.contains("Removed ancestorid from default indexes")
-+    topology_st.logcap.flush()
-+
-+    log.info("Verify ancestorid was removed from default indexes")
-+    dse_ldif = DSEldif(standalone)  # Reload to get fresh data
-+    cn_value = dse_ldif.get(ancestorid_default_dn, "cn", single=True)
-+    assert cn_value is None, f"ancestorid should be removed from default indexes, but got: {cn_value}"
-+    log.info("ancestorid successfully removed from default indexes")
-+
-+    log.info("Start the server")
-+    standalone.start()
-+
-+
-+def test_index_check_fixes_multiple_issues(topology_st):
-+    """Check if dsctl index-check --fix handles multiple issues at once
-+
-+    :id: 8e3f6a1b-2c5d-4f7e-3a9b-0c1d2e4f5a6b
-+    :setup: Standalone instance
-+    :steps:
-+        1. Create DS instance
-+        2. Stop the server
-+        3. Add multiple issues: scanlimit, ancestorid config, missing matching rule
-+        4. Run dsctl index-check (should detect all issues)
-+        5. Run dsctl index-check --fix
-+        6. Verify all issues were fixed
-+        7. Run dsctl index-check again (should pass)
-+        8. Start the server
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. Success
-+        4. index-check returns False and detects all issues
-+        5. index-check returns True after fix
-+        6. All issues resolved
-+        7. index-check returns True (no issues)
-+        8. Success
-+    """
-+    from lib389.cli_ctl.dbtasks import dbtasks_index_check
-+    from lib389.dseldif import DSEldif
-+
-+    standalone = topology_st.standalone
-+
-+    log.info("Stop the server")
-+    standalone.stop()
-+
-+    dse_ldif = DSEldif(standalone)
-+    parentid_dn = "cn=parentid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config"
-+    ancestorid_dn = "cn=ancestorid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config"
-+
-+    log.info("Add issue 1: nsIndexIDListScanLimit to parentid")
-+    dse_ldif.add(parentid_dn, "nsIndexIDListScanLimit", "4000")
-+
-+    log.info("Add issue 2: ancestorid index config entry")
-+    ancestorid_entry = [
-+        f"dn: {ancestorid_dn}\n",
-+        "objectClass: top\n",
-+        "objectClass: nsIndex\n",
-+        "cn: ancestorid\n",
-+        "nsSystemIndex: true\n",
-+        "nsIndexType: eq\n",
-+    ]
-+    dse_ldif.add_entry(ancestorid_entry)
-+
-+    log.info("Add issue 3: Remove integerOrderingMatch from parentid")
-+    dse_ldif = DSEldif(standalone)  # Reload
-+    matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
-+    if matching_rules:
-+        for mr in matching_rules:
-+            if "integerorderingmatch" in mr.lower():
-+                dse_ldif.delete(parentid_dn, "nsMatchingRule", mr)
-+
-+    log.info("Run index-check without --fix (should detect all issues)")
-+    args = FakeArgs()
-+    args.backend = "userRoot"
-+    args.fix = False
-+
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is False, "index-check should detect multiple issues"
-+    # Check that multiple issues were detected
-+    assert topology_st.logcap.contains("nsIndexIDListScanLimit")
-+    assert topology_st.logcap.contains("ancestorid")
-+    topology_st.logcap.flush()
-+
-+    log.info("Run index-check with --fix")
-+    args.fix = True
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is True, "index-check --fix should succeed"
-+    assert topology_st.logcap.contains("All issues fixed")
-+    topology_st.logcap.flush()
-+
-+    log.info("Verify all issues were fixed")
-+    dse_ldif = DSEldif(standalone)  # Reload
-+
-+    # Check scanlimit removed
-+    scanlimit = dse_ldif.get(parentid_dn, "nsIndexIDListScanLimit", single=True)
-+    assert scanlimit is None, f"nsIndexIDListScanLimit should be removed, got: {scanlimit}"
-+
-+    # Check ancestorid config removed
-+    cn_value = dse_ldif.get(ancestorid_dn, "cn", single=True)
-+    assert cn_value is None, f"ancestorid config should be removed, got: {cn_value}"
-+
-+    # Check matching rule added back
-+    matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
-+    found_int_order = False
-+    if matching_rules:
-+        for mr in matching_rules:
-+            if "integerorderingmatch" in mr.lower():
-+                found_int_order = True
-+                break
-+    assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}"
-+
-+    log.info("All issues verified as fixed")
-+
-+    log.info("Run index-check again to confirm all clear")
-+    args.fix = False
-+    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
-+    assert result is True, "index-check should pass after fix"
-+    assert topology_st.logcap.contains("All checks passed")
-+    topology_st.logcap.flush()
-+
-+    log.info("Start the server")
-+    standalone.start()
-+
-+
- if __name__ == "__main__":
-     # Run isolated
-     # -s for DEBUG mode
-diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
-index 51bfd7e77..ae359ab6b 100644
---- a/rpm/389-ds-base.spec.in
-+++ b/rpm/389-ds-base.spec.in
-@@ -628,42 +628,45 @@ if ! getent passwd $USERNAME >/dev/null ; then
- fi
- 
- # Reload our sysctl before we restart (if we can)
--sysctl --system &> $output; true
-+sysctl --system &> "$output"; true
- 
--# Gather the running instances so we can restart them
-+# Gather running instances, stop them, run index-check, then restart
- instbase="%{_sysconfdir}/%{pkgname}"
-+instances=""
- ninst=0
--for dir in $instbase/slapd-* ; do
--    echo dir = $dir >> $output 2>&1 || :
-+
-+for dir in "$instbase"/slapd-* ; do
-+    echo "dir = $dir" >> "$output" 2>&1 || :
-     if [ ! -d "$dir" ] ; then continue ; fi
-     case "$dir" in *.removed) continue ;; esac
--    basename=`basename $dir`
--    inst="%{pkgname}@`echo $basename | sed -e 's/slapd-//g'`"
--    echo found instance $inst - getting status  >> $output 2>&1 || :
--    if /bin/systemctl -q is-active $inst ; then
--       echo instance $inst is running >> $output 2>&1 || :
-+    basename=$(basename "$dir")
-+    inst="%{pkgname}@${basename#slapd-}"
-+    inst_name="${basename#slapd-}"
-+    echo "found instance $inst - getting status" >> "$output" 2>&1 || :
-+    if /bin/systemctl -q is-active "$inst" ; then
-+       echo "instance $inst is running - stopping for upgrade" >> "$output" 2>&1 || :
-        instances="$instances $inst"
-+       /bin/systemctl stop "$inst" >> "$output" 2>&1 || :
-     else
--       echo instance $inst is not running >> $output 2>&1 || :
-+       echo "instance $inst is not running" >> "$output" 2>&1 || :
-     fi
--    ninst=`expr $ninst + 1`
-+    # Run index-check on all instances (running or not)
-+    # This fixes index ordering mismatches from older versions
-+    dsctl "$inst_name" index-check --fix >> "$output2" 2>&1 || :
-+    ninst=$((ninst + 1))
- done
-+
- if [ $ninst -eq 0 ] ; then
--    echo no instances to upgrade >> $output 2>&1 || :
--    exit 0 # have no instances to upgrade - just skip the rest
--else
--    # restart running instances
--    echo shutting down all instances . . . >> $output 2>&1 || :
--    for inst in $instances ; do
--        echo stopping instance $inst >> $output 2>&1 || :
--        /bin/systemctl stop $inst >> $output 2>&1 || :
--    done
--    for inst in $instances ; do
--        echo starting instance $inst >> $output 2>&1 || :
--        /bin/systemctl start $inst >> $output 2>&1 || :
--    done
-+    echo "no instances to upgrade" >> "$output" 2>&1 || :
-+    exit 0
- fi
- 
-+# Restart previously running instances
-+for inst in $instances ; do
-+    echo "starting instance $inst" >> "$output" 2>&1 || :
-+    /bin/systemctl start "$inst" >> "$output" 2>&1 || :
-+done
-+
- 
- %preun
- if [ $1 -eq 0 ]; then # Final removal
-diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py
-index 856639672..16da966d1 100644
---- a/src/lib389/lib389/cli_ctl/dbtasks.py
-+++ b/src/lib389/lib389/cli_ctl/dbtasks.py
-@@ -7,12 +7,24 @@
- # See LICENSE for details.
- # --- END COPYRIGHT BLOCK ---
- 
-+import glob
- import os
-+import re
-+import subprocess
-+from enum import Enum
- from lib389._constants import TaskWarning
- from lib389.cli_base import CustomHelpFormatter
-+from lib389.dseldif import DSEldif
- from pathlib import Path
- 
- 
-+class IndexOrdering(Enum):
-+    """Represents the ordering type of an index."""
-+    INTEGER = "integer"
-+    LEXICOGRAPHIC = "lexicographic"
-+    UNKNOWN = "unknown"
-+
-+
- def dbtasks_db2index(inst, log, args):
-     rtn = False
-     if not args.backend:
-@@ -126,6 +138,387 @@ def dbtasks_verify(inst, log, args):
-         log.info("dbverify successful")
- 
- 
-+def _get_db_dir(dse_ldif):
-+    """Get the database directory.
-+
-+    Args:
-+        dse_ldif: DSEldif instance.
-+
-+    Returns:
-+        Path to the database directory, or None if not found.
-+    """
-+    try:
-+        db_dir = dse_ldif.get(
-+            "cn=config,cn=ldbm database,cn=plugins,cn=config",
-+            "nsslapd-directory",
-+            single=True,
-+        )
-+        return db_dir
-+    except (ValueError, TypeError):
-+        pass
-+    return None
-+
-+
-+
-+def _has_integer_ordering_match(dse_ldif, backend, index_name):
-+    """Check if an index has integerOrderingMatch configured in DSE.
-+
-+    Args:
-+        dse_ldif: DSEldif instance.
-+        backend: Backend name.
-+        index_name: Name of the index to check.
-+
-+    Returns:
-+        True if integerOrderingMatch is configured, False otherwise.
-+    """
-+    index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format(
-+        index_name, backend
-+    )
-+    matching_rules = dse_ldif.get(index_dn, "nsMatchingRule", lower=True)
-+    if matching_rules:
-+        return any(mr.lower() == "integerorderingmatch" for mr in matching_rules)
-+    return False
-+
-+
-+def _has_index_scan_limit(dse_ldif, backend, index_name):
-+    """Check if an index has nsIndexIDListScanLimit configured.
-+
-+    Args:
-+        dse_ldif: DSEldif instance.
-+        backend: Backend name.
-+        index_name: Name of the index to check.
-+
-+    Returns:
-+        True if nsIndexIDListScanLimit is configured, False otherwise.
-+    """
-+    index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format(
-+        index_name, backend
-+    )
-+    scan_limit = dse_ldif.get(index_dn, "nsIndexIDListScanLimit")
-+    return scan_limit is not None
-+
-+
-+def _index_config_exists(dse_ldif, backend, index_name):
-+    """Check if an index configuration entry exists in DSE.
-+
-+    Args:
-+        dse_ldif: DSEldif instance.
-+        backend: Backend name.
-+        index_name: Name of the index to check.
-+
-+    Returns:
-+        True if the index config entry exists, False otherwise.
-+    """
-+    index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format(
-+        index_name, backend
-+    )
-+    try:
-+        cn = dse_ldif.get(index_dn, "cn")
-+        return cn is not None
-+    except (ValueError, KeyError):
-+        return False
-+
-+
-+def _default_index_exists(dse_ldif, index_name):
-+    """Check if an index exists in cn=default indexes.
-+
-+    Args:
-+        dse_ldif: DSEldif instance.
-+        index_name: Name of the index to check.
-+
-+    Returns:
-+        True if the index exists in default indexes, False otherwise.
-+    """
-+    index_dn = "cn={},cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config".format(
-+        index_name
-+    )
-+    try:
-+        cn = dse_ldif.get(index_dn, "cn")
-+        return cn is not None
-+    except (ValueError, KeyError):
-+        return False
-+
-+
-+def _check_disk_ordering(db_dir, backend, index_name, dbscan_path, is_mdb, log):
-+    """Check if index on disk uses lexicographic or integer ordering.
-+
-+    Args:
-+        db_dir: Path to the database directory.
-+        backend: Backend name.
-+        index_name: Name of the index to check.
-+        dbscan_path: Path to the dbscan binary.
-+        is_mdb: True if using MDB backend.
-+        log: Logger instance.
-+
-+    Returns:
-+        IndexOrdering: The detected ordering type.
-+    """
-+    if is_mdb:
-+        # MDB uses pseudo-paths: db_dir/backend/index.db
-+        # dbscan accesses indexes via paths like: /var/lib/dirsrv/slapd-xxx/db/userroot/parentid.db
-+        index_file = os.path.join(db_dir, backend, "{}.db".format(index_name))
-+    else:
-+        # BDB has separate directories per backend with actual index files
-+        backend_dir = os.path.join(db_dir, backend)
-+        if not os.path.exists(backend_dir):
-+            return IndexOrdering.UNKNOWN
-+        index_file = None
-+        pattern = os.path.join(backend_dir, "{}.db*".format(index_name))
-+        for f in glob.glob(pattern):
-+            if os.path.isfile(f):
-+                index_file = f
-+                break
-+        if not index_file:
-+            return IndexOrdering.UNKNOWN
-+
-+    try:
-+        result = subprocess.run(
-+            [dbscan_path, "-f", index_file],
-+            stdout=subprocess.PIPE,
-+            stderr=subprocess.PIPE,
-+            universal_newlines=True,
-+            timeout=60,
-+        )
-+
-+        if result.returncode != 0:
-+            log.warning("  dbscan returned non-zero exit code for %s", index_file)
-+            return IndexOrdering.UNKNOWN
-+
-+        # Parse keys from dbscan output
-+        keys = []
-+        for line in result.stdout.split("\n"):
-+            line = line.strip()
-+            if line.startswith("="):
-+                match = re.match(r"^=(\d+)", line)
-+                if match:
-+                    keys.append(int(match.group(1)))
-+
-+        if len(keys) < 2:
-+            return IndexOrdering.UNKNOWN
-+
-+        # Check if keys are in integer order by looking for decreasing numeric values
-+        # (which would indicate lexicographic ordering, e.g., "3" < "30" < "4")
-+        prev_id = keys[0]
-+        for i in range(1, min(len(keys), 100)):
-+            current_id = keys[i]
-+            if prev_id > current_id:
-+                return IndexOrdering.LEXICOGRAPHIC
-+            prev_id = current_id
-+
-+        return IndexOrdering.INTEGER
-+
-+    except subprocess.TimeoutExpired:
-+        log.warning("  dbscan timed out for %s", index_file)
-+        return IndexOrdering.UNKNOWN
-+    except OSError as e:
-+        log.warning("  Error running dbscan: %s", e)
-+        return IndexOrdering.UNKNOWN
-+
-+
-+def dbtasks_index_check(inst, log, args):
-+    """Check and optionally fix index ordering mismatches.
-+
-+    This function detects mismatches between the configured ordering
-+    (integerOrderingMatch in DSE) and the actual on-disk ordering of
-+    parentid and ancestorid indexes.
-+
-+    Args:
-+        inst: DirSrv instance.
-+        log: Logger instance.
-+        args: Parsed command line arguments.
-+
-+    Returns:
-+        True if all checks passed, False if mismatches were detected.
-+    """
-+    # Server must be stopped
-+    if inst.status():
-+        log.error("index-check requires the instance to be stopped")
-+        return False
-+
-+    # Check for dbscan binary
-+    dbscan_path = os.path.join(inst.ds_paths.bin_dir, "dbscan")
-+    if not os.path.exists(dbscan_path):
-+        log.error("dbscan utility not found at %s", dbscan_path)
-+        return False
-+
-+    # Load DSE
-+    try:
-+        dse_ldif = DSEldif(inst)
-+    except Exception as e:
-+        log.error("Failed to read dse.ldif: %s", e)
-+        return False
-+
-+    # Get backends to check
-+    all_backends = dse_ldif.get_backends()
-+    if not all_backends:
-+        log.info("No backends found")
-+        return True
-+
-+    # Filter to specific backend if requested
-+    if args.backend:
-+        # Case-insensitive backend lookup
-+        backend_lower = args.backend.lower()
-+        matching_backend = None
-+        for be in all_backends:
-+            if be.lower() == backend_lower:
-+                matching_backend = be
-+                break
-+        if matching_backend is None:
-+            log.error("Backend '%s' not found. Available backends: %s",
-+                      args.backend, ", ".join(all_backends))
-+            return False
-+        backends_to_check = [matching_backend]
-+    else:
-+        backends_to_check = all_backends
-+
-+    # Get database directory and check database type
-+    db_dir = _get_db_dir(dse_ldif)
-+    if not db_dir or not os.path.exists(db_dir):
-+        log.error("Database directory not found")
-+        return False
-+
-+    db_lib = inst.get_db_lib()
-+    is_mdb = (db_lib == "mdb")
-+    log.info("Database type: %s", db_lib.upper())
-+
-+    # Track all issues found
-+    all_ok = True
-+    mismatches = []  # (backend, index_name) tuples needing reindex
-+    missing_matching_rules = []  # (backend, index_name) tuples missing integerOrderingMatch
-+    scan_limits_to_remove = []  # (backend, index_name) tuples with nsIndexIDListScanLimit
-+    ancestorid_configs_to_remove = []  # backend names with ancestorid config entries
-+    remove_ancestorid_from_defaults = False  # Flag to remove from cn=default indexes
-+
-+    # Check if ancestorid exists in cn=default indexes (should be removed)
-+    if _default_index_exists(dse_ldif, "ancestorid"):
-+        log.warning("ancestorid found in cn=default indexes - should be removed")
-+        remove_ancestorid_from_defaults = True
-+        all_ok = False
-+
-+    for backend in backends_to_check:
-+        log.info("Checking backend: %s", backend)
-+
-+        # Check for ancestorid config entry (should not exist)
-+        if _index_config_exists(dse_ldif, backend, "ancestorid"):
-+            log.warning("  ancestorid - config entry exists (should be removed)")
-+            ancestorid_configs_to_remove.append(backend)
-+            all_ok = False
-+
-+        # Check parentid and ancestorid indexes
-+        for index_name in ["parentid", "ancestorid"]:
-+            # Check for scan limits (should be removed)
-+            if _has_index_scan_limit(dse_ldif, backend, index_name):
-+                log.warning("  %s - has nsIndexIDListScanLimit (should be removed)", index_name)
-+                scan_limits_to_remove.append((backend, index_name))
-+                all_ok = False
-+
-+            # Check disk ordering
-+            disk_ordering = _check_disk_ordering(db_dir, backend, index_name, dbscan_path, is_mdb, log)
-+
-+            if disk_ordering == IndexOrdering.UNKNOWN:
-+                log.info("  %s - could not determine disk ordering, skipping", index_name)
-+                # For parentid, still check if matching rule is missing
-+                if index_name == "parentid":
-+                    config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name)
-+                    if not config_has_int_order:
-+                        log.warning("  %s - missing integerOrderingMatch in config", index_name)
-+                        missing_matching_rules.append((backend, index_name))
-+                        all_ok = False
-+                continue
-+
-+            config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name)
-+            config_desc = "integer" if config_has_int_order else "lexicographic"
-+            log.info("  %s - config: %s, disk: %s",
-+                     index_name, config_desc, disk_ordering.value)
-+
-+            # For parentid, the desired state is always integer ordering
-+            if index_name == "parentid":
-+                if not config_has_int_order:
-+                    log.warning("  %s - missing integerOrderingMatch in config", index_name)
-+                    if (backend, index_name) not in missing_matching_rules:
-+                        missing_matching_rules.append((backend, index_name))
-+                    all_ok = False
-+
-+                if disk_ordering == IndexOrdering.LEXICOGRAPHIC:
-+                    log.warning("  %s - disk ordering is lexicographic, needs reindex", index_name)
-+                    if (backend, index_name) not in mismatches:
-+                        mismatches.append((backend, index_name))
-+                    all_ok = False
-+
-+    # Handle issues
-+    if not all_ok:
-+        if args.fix:
-+            log.info("Fixing issues...")
-+
-+            # Remove ancestorid from cn=default indexes
-+            if remove_ancestorid_from_defaults:
-+                default_idx_dn = "cn=ancestorid,cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config"
-+                log.info("  Removing ancestorid from default indexes...")
-+                try:
-+                    dse_ldif.delete_dn(default_idx_dn)
-+                    log.info("  Removed ancestorid from default indexes")
-+                except Exception as e:
-+                    log.error("  Failed to remove ancestorid from default indexes: %s", e)
-+                    return False
-+
-+            # Remove scan limits (only for indexes that won't be deleted)
-+            for backend, index_name in scan_limits_to_remove:
-+                # Skip ancestorid if we're going to delete the whole entry anyway
-+                if index_name == "ancestorid" and backend in ancestorid_configs_to_remove:
-+                    continue
-+                index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format(
-+                    index_name, backend
-+                )
-+                log.info("  Removing nsIndexIDListScanLimit from %s in backend %s...", index_name, backend)
-+                try:
-+                    dse_ldif.delete(index_dn, "nsIndexIDListScanLimit")
-+                    log.info("  Removed nsIndexIDListScanLimit from %s", index_name)
-+                except Exception as e:
-+                    log.error("  Failed to remove nsIndexIDListScanLimit from %s: %s", index_name, e)
-+                    return False
-+
-+            # Remove ancestorid config entries from backends
-+            for backend in ancestorid_configs_to_remove:
-+                index_dn = "cn=ancestorid,cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format(backend)
-+                log.info("  Removing ancestorid config entry from backend %s...", backend)
-+                try:
-+                    dse_ldif.delete_dn(index_dn)
-+                    log.info("  Removed ancestorid config entry from backend %s", backend)
-+                except Exception as e:
-+                    log.error("  Failed to remove ancestorid config from backend %s: %s", backend, e)
-+                    return False
-+
-+            # Add missing matching rules to dse.ldif
-+            for backend, index_name in missing_matching_rules:
-+                index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format(
-+                    index_name, backend
-+                )
-+                log.info("  Adding integerOrderingMatch to %s in backend %s...", index_name, backend)
-+                try:
-+                    dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch")
-+                    log.info("  Updated dse.ldif with integerOrderingMatch for %s", index_name)
-+                except Exception as e:
-+                    log.error("  Failed to update dse.ldif for %s: %s", index_name, e)
-+                    return False
-+
-+            # Reindex indexes with disk ordering issues
-+            for backend, index_name in mismatches:
-+                log.info("  Reindexing %s in backend %s...", index_name, backend)
-+                if not inst.db2index(bename=backend, attrs=[index_name]):
-+                    log.error("  Failed to reindex %s", index_name)
-+                    return False
-+                log.info("  Reindex of %s completed successfully", index_name)
-+
-+            log.info("All issues fixed")
-+            return True
-+        else:
-+            log.info("Issues detected. Run with --fix to repair.")
-+            return False
-+    else:
-+        log.info("All checks passed - no issues found")
-+        return True
-+
-+
- def create_parser(subcommands):
-     db2index_parser = subcommands.add_parser('db2index', help="Initialise a reindex of the server database. The server must be stopped for this to proceed.", formatter_class=CustomHelpFormatter)
-     # db2index_parser.add_argument('suffix', help="The suffix to reindex. IE dc=example,dc=com.")
-@@ -172,3 +565,12 @@ def create_parser(subcommands):
-     ldifs_parser = subcommands.add_parser('ldifs', help="List all the LDIF files located in the server's LDIF directory", formatter_class=CustomHelpFormatter)
-     ldifs_parser.add_argument('--delete', nargs=1, help="Delete LDIF file")
-     ldifs_parser.set_defaults(func=dbtasks_ldifs)
-+
-+    index_check_parser = subcommands.add_parser('index-check',
-+        help="Check for index ordering mismatches (parentid/ancestorid). The server must be stopped.",
-+        formatter_class=CustomHelpFormatter)
-+    index_check_parser.add_argument('backend', nargs='?', default=None,
-+        help="Backend to check. If not specified, all backends are checked.")
-+    index_check_parser.add_argument('--fix', action='store_true', default=False,
-+        help="Fix mismatches by reindexing affected indexes")
-+    index_check_parser.set_defaults(func=dbtasks_index_check)
-diff --git a/src/lib389/lib389/dseldif.py b/src/lib389/lib389/dseldif.py
-index d12c6424c..7834d9468 100644
---- a/src/lib389/lib389/dseldif.py
-+++ b/src/lib389/lib389/dseldif.py
-@@ -125,11 +125,14 @@ class DSEldif(DSLint):
-             self._contents[i] = self._contents[i].replace(strfrom, strto)
-         self._update()
- 
--    def _find_attr(self, entry_dn, attr):
-+    def _find_attr(self, entry_dn, attr, lower=False):
-         """Find all attribute values and indexes under a given entry
- 
-         Returns entry dn index and attribute data dict:
-         relative attribute indexes and the attribute value
-+
-+        :param lower: Use case-insensitive matching for attribute name
-+        :type lower: boolean
-         """
- 
-         entry_dn_i = self._contents.index("dn: {}\n".format(entry_dn.lower()))
-@@ -146,7 +149,11 @@ class DSEldif(DSLint):
- 
-         # Find the attribute
-         for line in entry_slice:
--            if line.startswith("{}:".format(attr)):
-+            if lower:
-+                match = line.lower().startswith("{}:".format(attr.lower()))
-+            else:
-+                match = line.startswith("{}:".format(attr))
-+            if match:
-                 attr_value = line.split(" ", 1)[1][:-1]
-                 attr_data.update({entry_slice.index(line): attr_value})
- 
-@@ -155,7 +162,7 @@ class DSEldif(DSLint):
- 
-         return entry_dn_i, attr_data
- 
--    def get(self, entry_dn, attr, single=False):
-+    def get(self, entry_dn, attr, single=False, lower=False):
-         """Return attribute values under a given entry
- 
-         :param entry_dn: a DN of entry we want to get attribute from
-@@ -163,11 +170,13 @@ class DSEldif(DSLint):
-         :param attr: an attribute name
-         :type attr: str
-         :param single: Return a single value instead of a list
--        :type sigle: boolean
-+        :type single: boolean
-+        :param lower: Use case-insensitive matching for attribute name
-+        :type lower: boolean
-         """
- 
-         try:
--            _, attr_data = self._find_attr(entry_dn, attr)
-+            _, attr_data = self._find_attr(entry_dn, attr, lower=lower)
-         except ValueError:
-             return None
- 
-@@ -190,6 +199,38 @@ class DSEldif(DSLint):
- 
-         return indexes
- 
-+    def get_backends(self):
-+        """Return a list of backend names from DSE.
-+
-+        Returns backend names preserving their original case, as the
-+        database directory names on disk use the original case.
-+
-+        Note: DSEldif lowercases DN lines, so we read the 'cn' attribute
-+        from each entry to get the original case.
-+
-+        :returns: List of backend names
-+        """
-+        backends = []
-+        excluded = ("config", "monitor", "index", "encrypted attributes")
-+
-+        for entry in self._contents:
-+            if (entry.startswith("dn: cn=") and
-+                    ",cn=ldbm database,cn=plugins,cn=config" in entry):
-+                parts = entry.split(",")
-+                if len(parts) > 1:
-+                    cn_lower = parts[0].replace("dn: cn=", "")
-+                    if cn_lower not in excluded:
-+                        dn = entry.strip()[4:].strip()
-+                        try:
-+                            suffix = self.get(dn, "nsslapd-suffix")
-+                            if suffix:
-+                                cn_values = self.get(dn, "cn")
-+                                if cn_values:
-+                                    backends.append(cn_values[0])
-+                        except (ValueError, IndexError):
-+                            pass
-+
-+        return list(set(backends))
- 
-     def add_entry(self, entry):
-         """Add a new entry
--- 
-2.52.0
-

diff --git a/0023-Issue-7184-2nd-argparse.HelpFormatter-_format_action.patch b/0023-Issue-7184-2nd-argparse.HelpFormatter-_format_action.patch
deleted file mode 100644
index 616bf44..0000000
--- a/0023-Issue-7184-2nd-argparse.HelpFormatter-_format_action.patch
+++ /dev/null
@@ -1,41 +0,0 @@
-From fcec1f00b99b227d44a2f0aef6e3a87ddb7e52a9 Mon Sep 17 00:00:00 2001
-From: Viktor Ashirov <vashirov@redhat.com>
-Date: Fri, 13 Feb 2026 15:38:52 +0100
-Subject: [PATCH] Issue 7184 - (2nd) argparse.HelpFormatter
- _format_actions_usage() is deprecated (#7257)
-
-Description:
-`_format_actions_usage()` was also removed in Python 3.14.3.
-Replace version check with `isinstance()` to handle the return type of
-`_get_actions_usage_parts()` more robustly across Python versions.
-
-Relates: https://github.com/389ds/389-ds-base/issues/7184
-Fixes: https://github.com/389ds/389-ds-base/issues/7253
-
-Reviewed by: @progier389 (Thanks!)
----
- src/lib389/lib389/cli_base/__init__.py | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py
-index f1055aadc..3af8a46e6 100644
---- a/src/lib389/lib389/cli_base/__init__.py
-+++ b/src/lib389/lib389/cli_base/__init__.py
-@@ -420,11 +420,11 @@ class CustomHelpFormatter(argparse.HelpFormatter):
-         else:
-             # Use _get_actions_usage_parts() for Python 3.13 and later
-             action_parts = self._get_actions_usage_parts(parent_arguments, [])
--            if sys.version_info >= (3, 15):
--                # Python 3.15 returns a tuple (list of actions, count of actions)
-+            if isinstance(action_parts, tuple):
-+                # Python 3.14.3+ and 3.15+ return a tuple (list of actions, count of actions)
-                 formatted_options = ' '.join(action_parts[0])
-             else:
--                # Python 3.13 and 3.14 return a list of actions
-+                # Earlier versions return a list of actions
-                 formatted_options = ' '.join(action_parts)
- 
-         # If formatted_options already in usage - remove them
--- 
-2.52.0
-

diff --git a/0024-Issue-7223-Use-lexicographical-order-for-ancestorid-.patch b/0024-Issue-7223-Use-lexicographical-order-for-ancestorid-.patch
deleted file mode 100644
index c0c9844..0000000
--- a/0024-Issue-7223-Use-lexicographical-order-for-ancestorid-.patch
+++ /dev/null
@@ -1,35 +0,0 @@
-From 7e575cc8cc6f1bf558f50ca0fc55145e469d60d2 Mon Sep 17 00:00:00 2001
-From: Viktor Ashirov <vashirov@redhat.com>
-Date: Fri, 13 Feb 2026 16:58:24 +0100
-Subject: [PATCH 1/2] Issue 7223 - Use lexicographical order for ancestorid
- (#7256)
-
-Description:
-`ldbm_instance_create_default_indexes()` configured ancestorid with
-integerOrderingMatch in the in-memory attrinfo, but ancestorid on disk
-might be using lexicographic ordering (data before the upgrade or after
-ldif2db import).
-
-Relates: https://github.com/389ds/389-ds-base/issues/7223
-
-Reviewed by: @tbordaz (Thanks!)
----
- ldap/servers/slapd/back-ldbm/instance.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
-index 17bfc09a0..1569eb7ff 100644
---- a/ldap/servers/slapd/back-ldbm/instance.c
-+++ b/ldap/servers/slapd/back-ldbm/instance.c
-@@ -231,7 +231,7 @@ ldbm_instance_create_default_indexes(backend *be)
-      * ancestorid is special, there is actually no such attr type
-      * but we still want to use the attr index file APIs.
-      */
--    e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
-+    e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, 0);
-     attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
-     slapi_entry_free(e);
- 
--- 
-2.52.0
-

diff --git a/0025-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch b/0025-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch
deleted file mode 100644
index a5c1292..0000000
--- a/0025-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch
+++ /dev/null
@@ -1,538 +0,0 @@
-From 273e0a92d902a2f2989f8def0f4fb42b7c08cae9 Mon Sep 17 00:00:00 2001
-From: Viktor Ashirov <vashirov@redhat.com>
-Date: Wed, 18 Feb 2026 09:26:57 +0100
-Subject: [PATCH] Issue 7223 - Remove integerOrderingMatch requirement for
- parentid (#7264)
-
-Description:
-integerOrderingMatch was introduced as a requirement for parentid and
-ancestorid indexes for performance reasons. But after #7096 the order
-for parentid doesn't make a lot of difference.
-
-Fix Description:
-* Remove integerOrderingMatch requirement for parentid.
-* Read only first 100 keys from dbscan in index ordering check
-* Do not run dsctl index-check during RPM upgrade
-
-Relates: https://github.com/389ds/389-ds-base/pull/7223
-
-Reviewed by: @progier389, @tbordaz (Thanks!)
----
- .../healthcheck/health_system_indexes_test.py |  83 ++++----------
- ldap/servers/slapd/upgrade.c                  | 105 ------------------
- rpm/389-ds-base.spec.in                       |   3 -
- src/lib389/lib389/backend.py                  |   5 +-
- src/lib389/lib389/cli_ctl/dbtasks.py          |  99 ++++++++---------
- 5 files changed, 73 insertions(+), 222 deletions(-)
-
-diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-index 6fbcf666c..9c48bd79b 100644
---- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
-@@ -180,7 +180,8 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
- 
- 
- def test_missing_matching_rule(topology_st, log_buffering_enabled):
--    """Check if healthcheck returns DSBLE0007 code when parentId index is missing integerOrderingMatch
-+    """Check that healthcheck does NOT report DSBLE0007 when parentId index is missing integerOrderingMatch.
-+    Both lexicographic and integer orderings are valid for parentid.
- 
-     :id: 7ffa71db-8995-430a-bed8-59bce944221c
-     :setup: Standalone instance
-@@ -190,19 +191,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
-         3. Use healthcheck without --json option
-         4. Use healthcheck with --json option
-         5. Re-add the matching rule
--        6. Use healthcheck without --json option
--        7. Use healthcheck with --json option
-     :expectedresults:
-         1. Success
-         2. Success
--        3. healthcheck reports DSBLE0007 code and related details
--        4. healthcheck reports DSBLE0007 code and related details
-+        3. healthcheck reports no issues found
-+        4. healthcheck reports no issues found
-         5. Success
--        6. healthcheck reports no issues found
--        7. healthcheck reports no issues found
-     """
- 
--    RET_CODE = "DSBLE0007"
-     PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
- 
-     standalone = topology_st.standalone
-@@ -211,17 +207,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
-     parentid_index = Index(standalone, PARENTID_DN)
-     parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
- 
--    run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
--    run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
-+    run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
-+    run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
- 
-     log.info("Re-add the integerOrderingMatch matching rule")
-     parentid_index = Index(standalone, PARENTID_DN)
-     parentid_index.add("nsMatchingRule", "integerOrderingMatch")
-     standalone.restart()
- 
--    run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
--    run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
--
- 
- def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffering_enabled):
-     """Check if healthcheck returns DSBLE0007 code when USN plugin is enabled but entryusn index is missing
-@@ -911,7 +904,9 @@ def test_index_check_fixes_ancestorid_config(topology_st):
- 
- 
- def test_index_check_fixes_missing_matching_rule(topology_st):
--    """Check if dsctl index-check --fix adds missing integerOrderingMatch
-+    """Check that removing integerOrderingMatch from parentid config is not
-+    flagged as an issue when disk ordering cannot be determined.
-+    Both lexicographic and integer orderings are valid for parentid.
- 
-     :id: 6c1d4e9f-0a3b-4d5c-1e7f-8a9b0c2d3e4f
-     :setup: Standalone instance
-@@ -919,18 +914,14 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
-         1. Create DS instance
-         2. Stop the server
-         3. Remove integerOrderingMatch from parentid index using DSEldif
--        4. Run dsctl index-check (should detect issue)
--        5. Run dsctl index-check --fix
--        6. Verify integerOrderingMatch was added back
--        7. Start the server
-+        4. Run dsctl index-check (should NOT detect issue since disk ordering is unknown)
-+        5. Start the server
-     :expectedresults:
-         1. Success
-         2. Success
-         3. Success
--        4. index-check returns False and detects missing matching rule
--        5. index-check returns True after fix
--        6. integerOrderingMatch is present
--        7. Success
-+        4. index-check returns True (no issues, disk ordering unknown)
-+        5. Success
-     """
-     from lib389.cli_ctl.dbtasks import dbtasks_index_check
-     from lib389.dseldif import DSEldif
-@@ -964,34 +955,20 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
-                 f"integerOrderingMatch should be removed, but found: {mr}"
-     log.info("integerOrderingMatch removed from parentid index")
- 
--    log.info("Run index-check without --fix (should detect issue)")
-+    log.info("Run index-check (should NOT detect issue - disk ordering unknown)")
-     args = FakeArgs()
-     args.backend = "userRoot"
-     args.fix = False
- 
-     result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
--    assert result is False, "index-check should detect missing matching rule"
--    assert topology_st.logcap.contains("missing integerOrderingMatch")
-+    assert result is True, \
-+        "index-check should not flag missing integerOrderingMatch when disk ordering is unknown"
-+    assert topology_st.logcap.contains("could not determine disk ordering")
-     topology_st.logcap.flush()
- 
--    log.info("Run index-check with --fix")
--    args.fix = True
--    result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
--    assert result is True, "index-check --fix should succeed"
--    assert topology_st.logcap.contains("integerOrderingMatch")
--    topology_st.logcap.flush()
--
--    log.info("Verify integerOrderingMatch was added back")
--    dse_ldif = DSEldif(standalone)  # Reload to get fresh data
--    matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
--    assert matching_rules is not None, "nsMatchingRule should be present"
--    found_int_order = False
--    for mr in matching_rules:
--        if "integerorderingmatch" in mr.lower():
--            found_int_order = True
--            break
--    assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}"
--    log.info("integerOrderingMatch successfully added back")
-+    log.info("Restore integerOrderingMatch and start the server")
-+    dse_ldif = DSEldif(standalone)
-+    dse_ldif.add(parentid_dn, "nsMatchingRule", "integerOrderingMatch")
- 
-     log.info("Start the server")
-     standalone.start()
-@@ -1081,7 +1058,7 @@ def test_index_check_fixes_multiple_issues(topology_st):
-     :steps:
-         1. Create DS instance
-         2. Stop the server
--        3. Add multiple issues: scanlimit, ancestorid config, missing matching rule
-+        3. Add multiple issues: scanlimit and ancestorid config
-         4. Run dsctl index-check (should detect all issues)
-         5. Run dsctl index-check --fix
-         6. Verify all issues were fixed
-@@ -1123,14 +1100,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
-     ]
-     dse_ldif.add_entry(ancestorid_entry)
- 
--    log.info("Add issue 3: Remove integerOrderingMatch from parentid")
--    dse_ldif = DSEldif(standalone)  # Reload
--    matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
--    if matching_rules:
--        for mr in matching_rules:
--            if "integerorderingmatch" in mr.lower():
--                dse_ldif.delete(parentid_dn, "nsMatchingRule", mr)
--
-     log.info("Run index-check without --fix (should detect all issues)")
-     args = FakeArgs()
-     args.backend = "userRoot"
-@@ -1161,16 +1130,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
-     cn_value = dse_ldif.get(ancestorid_dn, "cn", single=True)
-     assert cn_value is None, f"ancestorid config should be removed, got: {cn_value}"
- 
--    # Check matching rule added back
--    matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
--    found_int_order = False
--    if matching_rules:
--        for mr in matching_rules:
--            if "integerorderingmatch" in mr.lower():
--                found_int_order = True
--                break
--    assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}"
--
-     log.info("All issues verified as fixed")
- 
-     log.info("Run index-check again to confirm all clear")
-diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
-index 6b1b012da..9557e9066 100644
---- a/ldap/servers/slapd/upgrade.c
-+++ b/ldap/servers/slapd/upgrade.c
-@@ -551,107 +551,6 @@ upgrade_remove_ancestorid_index_config(void)
-     return uresult;
- }
- 
--/*
-- * Check if parentid/ancestorid indexes are missing the integerOrderingMatch
-- * matching rule.
-- *
-- * This function logs a warning if we detect this condition, advising
-- * the administrator to reindex the affected attributes.
-- */
--static upgrade_status
--upgrade_check_id_index_matching_rule(void)
--{
--    struct slapi_pblock *pb = slapi_pblock_new();
--    Slapi_Entry **backends = NULL;
--    const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
--    const char *be_filter = "(objectclass=nsBackendInstance)";
--    const char *attrs_to_check[] = {"parentid", NULL};
--    upgrade_status uresult = UPGRADE_SUCCESS;
--
--    /* Search for all backend instances */
--    slapi_search_internal_set_pb(
--            pb, be_base_dn,
--            LDAP_SCOPE_ONELEVEL,
--            be_filter, NULL, 0, NULL, NULL,
--            plugin_get_default_component_id(), 0);
--    slapi_search_internal_pb(pb);
--    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
--
--    if (backends) {
--        for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
--            const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
--            const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
--            if (!be_dn || !be_name) {
--                continue;
--            }
--
--            /* Check each attribute that should have integerOrderingMatch */
--            for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
--                const char *attr_name = attrs_to_check[attr_idx];
--                struct slapi_pblock *idx_pb = slapi_pblock_new();
--                Slapi_Entry **idx_entries = NULL;
--                char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s",
--                                                       attr_name, be_dn);
--                char *idx_filter = "(objectclass=nsIndex)";
--                PRBool has_matching_rule = PR_FALSE;
--
--                if (!idx_dn) {
--                    slapi_pblock_destroy(idx_pb);
--                    continue;
--                }
--
--                slapi_search_internal_set_pb(
--                        idx_pb, idx_dn,
--                        LDAP_SCOPE_BASE,
--                        idx_filter, NULL, 0, NULL, NULL,
--                        plugin_get_default_component_id(), 0);
--                slapi_search_internal_pb(idx_pb);
--                slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
--
--                if (idx_entries && idx_entries[0]) {
--                    /* Index exists, check if it has integerOrderingMatch */
--                    Slapi_Attr *mr_attr = NULL;
--                    if (slapi_entry_attr_find(idx_entries[0], "nsMatchingRule", &mr_attr) == 0) {
--                        Slapi_Value *sval = NULL;
--                        int idx;
--                        for (idx = slapi_attr_first_value(mr_attr, &sval);
--                             idx != -1;
--                             idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
--                            const struct berval *bval = slapi_value_get_berval(sval);
--                            if (bval && bval->bv_val &&
--                                strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
--                                has_matching_rule = PR_TRUE;
--                                break;
--                            }
--                        }
--                    }
--
--                    if (!has_matching_rule) {
--                        /* Index exists but doesn't have integerOrderingMatch, log a warning */
--                        slapi_log_err(SLAPI_LOG_ERR, "upgrade_check_id_index_matching_rule",
--                                "Index '%s' in backend '%s' is missing 'nsMatchingRule: integerOrderingMatch'. "
--                                "Incorrectly configured system indexes can lead to poor search performance, replication issues, and other operational problems. "
--                                "To fix this, add the matching rule and reindex: "
--                                "dsconf <instance> backend index set --add-mr integerOrderingMatch --attr %s %s && "
--                                "dsconf <instance> backend index reindex --attr %s %s. "
--                                "WARNING: Reindexing can be resource-intensive and may impact server performance on a live system. "
--                                "Consider scheduling reindexing during maintenance windows or periods of low activity.\n",
--                                attr_name, be_name, attr_name, be_name, attr_name, be_name);
--                    }
--                }
--
--                slapi_ch_free_string(&idx_dn);
--                slapi_free_search_results_internal(idx_pb);
--                slapi_pblock_destroy(idx_pb);
--            }
--        }
--    }
--
--    slapi_free_search_results_internal(pb);
--    slapi_pblock_destroy(pb);
--
--    return uresult;
--}
- 
- /*
-  * Upgrade the base config of the PAM PTA plugin.
-@@ -879,10 +778,6 @@ upgrade_server(void)
-         return UPGRADE_FAILURE;
-     }
- 
--    if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
--        return UPGRADE_FAILURE;
--    }
--
-     return UPGRADE_SUCCESS;
- }
- 
-diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
-index ae359ab6b..61a8cb368 100644
---- a/rpm/389-ds-base.spec.in
-+++ b/rpm/389-ds-base.spec.in
-@@ -650,9 +650,6 @@ for dir in "$instbase"/slapd-* ; do
-     else
-        echo "instance $inst is not running" >> "$output" 2>&1 || :
-     fi
--    # Run index-check on all instances (running or not)
--    # This fixes index ordering mismatches from older versions
--    dsctl "$inst_name" index-check --fix >> "$output2" 2>&1 || :
-     ninst=$((ninst + 1))
- done
- 
-diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
-index f3dbe7c92..6c8cbc018 100644
---- a/src/lib389/lib389/backend.py
-+++ b/src/lib389/lib389/backend.py
-@@ -647,9 +647,10 @@ class Backend(DSLdapObject):
-         # Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
-         # Note: entryrdn and ancestorid are internal system indexes that are not
-         # exposed in cn=config - they are managed internally by the server.
--        # Only parentid has a DSE config entry (for the integerOrderingMatch rule).
-+        # parentid works correctly with both lexicographic and integer ordering,
-+        # so integerOrderingMatch is not required.
-         expected_system_indexes = {
--            'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
-+            'parentid': {'types': ['eq'], 'matching_rule': None},
-             'objectClass': {'types': ['eq'], 'matching_rule': None},
-             'aci': {'types': ['pres'], 'matching_rule': None},
-             'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
-diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py
-index cd96cdaf7..b02de203f 100644
---- a/src/lib389/lib389/cli_ctl/dbtasks.py
-+++ b/src/lib389/lib389/cli_ctl/dbtasks.py
-@@ -10,6 +10,7 @@
- import glob
- import os
- import re
-+import signal
- import subprocess
- from enum import Enum
- from lib389._constants import TaskWarning
-@@ -263,45 +264,53 @@ def _check_disk_ordering(db_dir, backend, index_name, dbscan_path, is_mdb, log):
-         if not index_file:
-             return IndexOrdering.UNKNOWN
- 
-+    # Only read the first 100 lines from dbscan to avoid scanning the
-+    # entire index (which can take hours on large databases).
-     try:
--        result = subprocess.run(
-+        proc = subprocess.Popen(
-             [dbscan_path, "-f", index_file],
-             stdout=subprocess.PIPE,
-             stderr=subprocess.PIPE,
-             universal_newlines=True,
--            timeout=60,
-         )
- 
--        if result.returncode != 0:
--            log.warning("  dbscan returned non-zero exit code for %s", index_file)
--            return IndexOrdering.UNKNOWN
--
--        # Parse keys from dbscan output
-         keys = []
--        for line in result.stdout.split("\n"):
-+        line_count = 0
-+        assert proc.stdout is not None
-+        for line in proc.stdout:
-+            line_count += 1
-+            if line_count > 100:
-+                break
-             line = line.strip()
-             if line.startswith("="):
-                 match = re.match(r"^=(\d+)", line)
-                 if match:
-                     keys.append(int(match.group(1)))
- 
-+        proc.terminate()
-+        try:
-+            proc.wait(timeout=5)
-+        except subprocess.TimeoutExpired:
-+            proc.kill()
-+            proc.wait()
-+
-+        if proc.returncode not in (0, -signal.SIGTERM):
-+            log.warning("  dbscan returned non-zero exit code for %s", index_file)
-+            return IndexOrdering.UNKNOWN
-+
-         if len(keys) < 2:
-             return IndexOrdering.UNKNOWN
- 
-         # Check if keys are in integer order by looking for decreasing numeric values
-         # (which would indicate lexicographic ordering, e.g., "3" < "30" < "4")
-         prev_id = keys[0]
--        for i in range(1, min(len(keys), 100)):
--            current_id = keys[i]
-+        for current_id in keys[1:]:
-             if prev_id > current_id:
-                 return IndexOrdering.LEXICOGRAPHIC
-             prev_id = current_id
- 
-         return IndexOrdering.INTEGER
- 
--    except subprocess.TimeoutExpired:
--        log.warning("  dbscan timed out for %s", index_file)
--        return IndexOrdering.UNKNOWN
-     except OSError as e:
-         log.warning("  Error running dbscan: %s", e)
-         return IndexOrdering.UNKNOWN
-@@ -375,8 +384,7 @@ def dbtasks_index_check(inst, log, args):
- 
-     # Track all issues found
-     all_ok = True
--    mismatches = []  # (backend, index_name) tuples needing reindex
--    missing_matching_rules = []  # (backend, index_name) tuples missing integerOrderingMatch
-+    config_fixes = []  # (backend, index_name, action) tuples: action is "add_mr" or "remove_mr"
-     scan_limits_to_remove = []  # (backend, index_name) tuples with nsIndexIDListScanLimit
-     ancestorid_configs_to_remove = []  # backend names with ancestorid config entries
-     remove_ancestorid_from_defaults = False  # Flag to remove from cn=default indexes
-@@ -409,13 +417,6 @@ def dbtasks_index_check(inst, log, args):
- 
-             if disk_ordering == IndexOrdering.UNKNOWN:
-                 log.info("  %s - could not determine disk ordering, skipping", index_name)
--                # For parentid, still check if matching rule is missing
--                if index_name == "parentid":
--                    config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name)
--                    if not config_has_int_order:
--                        log.warning("  %s - missing integerOrderingMatch in config", index_name)
--                        missing_matching_rules.append((backend, index_name))
--                        all_ok = False
-                 continue
- 
-             config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name)
-@@ -423,18 +424,15 @@ def dbtasks_index_check(inst, log, args):
-             log.info("  %s - config: %s, disk: %s",
-                      index_name, config_desc, disk_ordering.value)
- 
--            # For parentid, the desired state is always integer ordering
-+            # Both orderings are valid for parentid, but config must match disk.
-             if index_name == "parentid":
--                if not config_has_int_order:
--                    log.warning("  %s - missing integerOrderingMatch in config", index_name)
--                    if (backend, index_name) not in missing_matching_rules:
--                        missing_matching_rules.append((backend, index_name))
-+                if config_has_int_order and disk_ordering == IndexOrdering.LEXICOGRAPHIC:
-+                    log.warning("  %s - MISMATCH: config has integerOrderingMatch but disk is lexicographic", index_name)
-+                    config_fixes.append((backend, index_name, "remove_mr"))
-                     all_ok = False
--
--                if disk_ordering == IndexOrdering.LEXICOGRAPHIC:
--                    log.warning("  %s - disk ordering is lexicographic, needs reindex", index_name)
--                    if (backend, index_name) not in mismatches:
--                        mismatches.append((backend, index_name))
-+                elif not config_has_int_order and disk_ordering == IndexOrdering.INTEGER:
-+                    log.warning("  %s - MISMATCH: config is lexicographic but disk has integer ordering", index_name)
-+                    config_fixes.append((backend, index_name, "add_mr"))
-                     all_ok = False
- 
-     # Handle issues
-@@ -480,26 +478,27 @@ def dbtasks_index_check(inst, log, args):
-                     log.error("  Failed to remove ancestorid config from backend %s: %s", backend, e)
-                     return False
- 
--            # Add missing matching rules to dse.ldif
--            for backend, index_name in missing_matching_rules:
-+            # Fix config-vs-disk ordering mismatches by adjusting config to match disk
-+            for backend, index_name, action in config_fixes:
-                 index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format(
-                     index_name, backend
-                 )
--                log.info("  Adding integerOrderingMatch to %s in backend %s...", index_name, backend)
--                try:
--                    dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch")
--                    log.info("  Updated dse.ldif with integerOrderingMatch for %s", index_name)
--                except Exception as e:
--                    log.error("  Failed to update dse.ldif for %s: %s", index_name, e)
--                    return False
--
--            # Reindex indexes with disk ordering issues
--            for backend, index_name in mismatches:
--                log.info("  Reindexing %s in backend %s...", index_name, backend)
--                if not inst.db2index(bename=backend, attrs=[index_name]):
--                    log.error("  Failed to reindex %s", index_name)
--                    return False
--                log.info("  Reindex of %s completed successfully", index_name)
-+                if action == "add_mr":
-+                    log.info("  Adding integerOrderingMatch to %s in backend %s...", index_name, backend)
-+                    try:
-+                        dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch")
-+                        log.info("  Updated dse.ldif with integerOrderingMatch for %s", index_name)
-+                    except Exception as e:
-+                        log.error("  Failed to update dse.ldif for %s: %s", index_name, e)
-+                        return False
-+                elif action == "remove_mr":
-+                    log.info("  Removing integerOrderingMatch from %s in backend %s...", index_name, backend)
-+                    try:
-+                        dse_ldif.delete(index_dn, "nsMatchingRule", "integerOrderingMatch")
-+                        log.info("  Removed integerOrderingMatch from %s", index_name)
-+                    except Exception as e:
-+                        log.error("  Failed to remove integerOrderingMatch from %s: %s", index_name, e)
-+                        return False
- 
-             log.info("All issues fixed")
-             return True
-@@ -563,5 +562,5 @@ def create_parser(subcommands):
-     index_check_parser.add_argument('backend', nargs='?', default=None,
-         help="Backend to check. If not specified, all backends are checked.")
-     index_check_parser.add_argument('--fix', action='store_true', default=False,
--        help="Fix mismatches by reindexing affected indexes")
-+        help="Fix mismatches by adjusting config to match on-disk data")
-     index_check_parser.set_defaults(func=dbtasks_index_check)
--- 
-2.53.0
-

diff --git a/0026-Security-fix-for-CVE-2025-14905.patch b/0026-Security-fix-for-CVE-2025-14905.patch
deleted file mode 100644
index 7469b61..0000000
--- a/0026-Security-fix-for-CVE-2025-14905.patch
+++ /dev/null
@@ -1,93 +0,0 @@
-From e2562f5894dd05a3b062e7820f471f2f8e12b85d Mon Sep 17 00:00:00 2001
-From: tbordaz <tbordaz@redhat.com>
-Date: Wed, 25 Feb 2026 14:06:42 +0100
-Subject: [PATCH] Security fix for CVE-2025-14905
-
-Description:
-	A vulnerability was found in the 389 Directory Server.
-	The 389 Directory Server present a risk of heap buffer overflow that
-	can be exploited to excute a Denial of Service and potential Remote
-	Code Execution
-
-References:
-    - https://access.redhat.com/security/cve/CVE-2025-14905
-    - https://bugzilla.redhat.com/show_bug.cgi?id=2423624
----
- ldap/servers/slapd/schema.c | 47 ++++++++++++++++++++++++++++++-------
- 1 file changed, 38 insertions(+), 9 deletions(-)
-
-diff --git a/ldap/servers/slapd/schema.c b/ldap/servers/slapd/schema.c
-index 9ef4ee4bf..7712a720d 100644
---- a/ldap/servers/slapd/schema.c
-+++ b/ldap/servers/slapd/schema.c
-@@ -1410,6 +1410,7 @@ schema_attr_enum_callback(struct asyntaxinfo *asip, void *arg)
-     const char *attr_desc, *syntaxoid;
-     char *outp, syntaxlengthbuf[128];
-     int i;
-+    int nb_aliases = 0;
- 
-     vals[0] = &val;
- 
-@@ -1435,6 +1436,7 @@ schema_attr_enum_callback(struct asyntaxinfo *asip, void *arg)
-     if (asip->asi_aliases != NULL) {
-         for (i = 0; asip->asi_aliases[i] != NULL; ++i) {
-             aliaslen += strlen(asip->asi_aliases[i]);
-+            nb_aliases++;
-         }
-     }
- 
-@@ -1452,15 +1454,42 @@ schema_attr_enum_callback(struct asyntaxinfo *asip, void *arg)
-      * XXX: 256 is a magic number... it must be big enough to account for
-      * all of the fixed sized items we output.
-      */
--    sizedbuffer_allocate(aew->psbAttrTypes, 256 + strlen(asip->asi_oid) +
--                                                strlen(asip->asi_name) +
--                                                aliaslen + strlen_null_ok(attr_desc) +
--                                                strlen(syntaxoid) +
--                                                strlen_null_ok(asip->asi_superior) +
--                                                strlen_null_ok(asip->asi_mr_equality) +
--                                                strlen_null_ok(asip->asi_mr_ordering) +
--                                                strlen_null_ok(asip->asi_mr_substring) +
--                                                strcat_extensions(NULL, asip->asi_extensions));
-+    {
-+        int asi_oid_strlen = strlen(asip->asi_oid) + 8;      /* "( %s NAME " */
-+        int asi_name_strlen = strlen(asip->asi_name) + 6;    /* "( '%s' ...)" */
-+        int asi_aliases_strlen = aliaslen + nb_aliases * 3;  /* "'%s' " */
-+        int asi_desc_strlen = strlen_null_ok(attr_desc) + 7; /* "DESC '%s'" */
-+        int asi_syntaxoid_strlen = strlen("SYNTAX ") + strlen(syntaxoid) + strlen(syntaxlengthbuf);
-+        int asi_superior_strlen = strlen("SUP ") + strlen_null_ok(asip->asi_superior);
-+        int asi_mr_equality_strlen = strlen("EQUALITY ") + strlen_null_ok(asip->asi_mr_equality);
-+        int asi_mr_ordering_strlen = strlen("ORDERING ") + strlen_null_ok(asip->asi_mr_ordering);
-+        int asi_mr_substring_strlen = strlen("SUBSTR ") + strlen_null_ok(asip->asi_mr_substring);
-+        int asi_flags_strlen = strlen("SINGLE-VALUE ") +
-+                               strlen(schema_obsolete_with_spaces) +
-+                               strlen(schema_collective_with_spaces) +
-+                               strlen(schema_nousermod_with_spaces) +
-+                               strlen("USAGE distributedOperation ") +
-+                               strlen("USAGE dSAOperation ") +
-+                               strlen("USAGE directoryOperation ");
-+        int asi_extension_strlen = strcat_extensions(NULL, asip->asi_extensions);
-+
-+        if (aew->enquote_sup_oc) {
-+            /* it enquote the syntax oid */
-+            asi_syntaxoid_strlen += 2;
-+        }
-+
-+    sizedbuffer_allocate(aew->psbAttrTypes, 256 + asi_oid_strlen +
-+                                                  asi_name_strlen +
-+                                                  asi_aliases_strlen +
-+                                                  asi_desc_strlen +
-+                                                  asi_syntaxoid_strlen +
-+                                                  asi_superior_strlen +
-+                                                  asi_mr_equality_strlen +
-+                                                  asi_mr_ordering_strlen +
-+                                                  asi_mr_substring_strlen +
-+                                                  asi_extension_strlen +
-+                                                  asi_flags_strlen);
-+    }
- 
-     /*
-      * Overall strategy is to maintain a pointer to the next location in
--- 
-2.53.0
-

diff --git a/0027-Issue-7302-dblib-bdb2mdb-fails-on-F43-F43-upgrade-73.patch b/0027-Issue-7302-dblib-bdb2mdb-fails-on-F43-F43-upgrade-73.patch
deleted file mode 100644
index f0054f9..0000000
--- a/0027-Issue-7302-dblib-bdb2mdb-fails-on-F43-F43-upgrade-73.patch
+++ /dev/null
@@ -1,70 +0,0 @@
-From 3f5f32ef36cf2c797c9379eba1ba2d8b2fd9841b Mon Sep 17 00:00:00 2001
-From: Viktor Ashirov <vashirov@redhat.com>
-Date: Fri, 6 Mar 2026 17:19:58 +0100
-Subject: [PATCH] Issue 7302 - dblib bdb2mdb fails on F43 -> F43 upgrade
- (#7303)
-
-Bug Description:
-`db->stat` is stubbed with `nothing()` which returns DB_SUCCESS without
-populating the stats output parameter. Both `bdb_get_page_count()` and
-`bdb_get_entries_count()` then dereference the NULL stats pointer,
-causing a segfault.
-
-Fix Descrption:
-Add NULL checks for the stats pointer after `db->stat()` calls in both
-`bdb_get_page_count()` and `bdb_get_entries_count()`.
-
-Fixes: https://github.com/389ds/389-ds-base/issues/7302
-
-Reviewed by: @tbordaz, @progier389 (Thanks!)
----
- .../slapd/back-ldbm/db-bdb/bdb_layer.c        | 20 +++++++++++++++----
- 1 file changed, 16 insertions(+), 4 deletions(-)
-
-diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
-index 5c61e30b2..4e5f9fd98 100644
---- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
-+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
-@@ -1788,11 +1788,17 @@ bdb_get_page_count(dbi_db_t *db, uint32_t *count)
-     rc = ((DB*)db)->stat(db, (DB_TXN*)txn, (void *)&stats, 0);
-     if (rc != 0) {
-         slapi_log_err(SLAPI_LOG_ERR, "bdb_get_page_count",
--                      "Failed to get bd statistics: db error - %d %s\n",
-+                      "Failed to get db statistics: db error - %d %s\n",
-                       rc, db_strerror(rc));
-         rc = DBI_RC_OTHER;
-+        *count = 0;
-+    } else if (stats == NULL) {
-+        slapi_log_err(SLAPI_LOG_INFO, "bdb_get_page_count",
-+                      "Failed to get db statistics: stats is NULL, defaulting page count to 0\n");
-+        *count = 0;
-+    } else {
-+        *count = stats->bt_pagecnt;
-     }
--    *count = rc ? 0 : stats->bt_pagecnt;
-     slapi_ch_free((void **)&stats);
-     return rc;
- }
-@@ -7084,11 +7090,17 @@ bdb_get_entries_count(dbi_db_t *db, dbi_txn_t *txn, int *count)
-     rc = ((DB*)db)->stat(db, (DB_TXN*)txn, (void *)&stats, 0);
-     if (rc != 0) {
-         slapi_log_err(SLAPI_LOG_ERR, "bdb_get_entries_count",
--                      "Failed to get bd statistics: db error - %d %s\n",
-+                      "Failed to get db statistics: db error - %d %s\n",
-                       rc, db_strerror(rc));
-         rc = DBI_RC_OTHER;
-+        *count = 0;
-+    } else if (stats == NULL) {
-+        slapi_log_err(SLAPI_LOG_INFO, "bdb_get_entries_count",
-+                      "Failed to get db statistics: stats is NULL, defaulting entries count to 0\n");
-+        *count = 0;
-+    } else {
-+        *count = stats->bt_ndata;
-     }
--    *count = rc ? 0 : stats->bt_ndata;
-     slapi_ch_free((void **)&stats);
-     return rc;
- }
--- 
-2.53.0
-

diff --git a/0028-Issue-7267-MDB_BAD_VALSIZE-error-when-updating-index.patch b/0028-Issue-7267-MDB_BAD_VALSIZE-error-when-updating-index.patch
deleted file mode 100644
index d1e9ec6..0000000
--- a/0028-Issue-7267-MDB_BAD_VALSIZE-error-when-updating-index.patch
+++ /dev/null
@@ -1,681 +0,0 @@
-From 4809c6cf43ac9709568e2637e127f5a76c9d845e Mon Sep 17 00:00:00 2001
-From: progier389 <progier@redhat.com>
-Date: Wed, 25 Feb 2026 18:00:24 +0100
-Subject: [PATCH] Issue 7267 - MDB_BAD_VALSIZE error when updating index
- (#7268)
-
-* Issue 7267 - MDB_BAD_VALSIZE error when updating index
-* Improve import log when writer fails
-* Fix Sourcery AI comments
-* Fix INDEX_KEY_LENGTH typo
-
-Problem with the key prefix handling when key is too long and must be hashed.
-The issue is that the # that is prepended is not reset when iterating over the valueset values (Ending up with very long prefix)
-
-Also refactored the code to avoid duplicate the code that prepare the key from the attribute value (used when updating the index or retrieving a value from an index)
-
-Issue: #7267
-
-Reviewed by: @tbordaz , @vashirov (Thanks!)
-
-Co-authored-by: Viktor Ashirov <vashirov@redhat.com>
-
----------
-
-Co-authored-by: Viktor Ashirov <vashirov@redhat.com>
----
- .../tests/suites/indexes/regression_test.py   |  85 +++++++++
- ldap/servers/slapd/back-ldbm/attrcrypt.h      |   2 +-
- ldap/servers/slapd/back-ldbm/back-ldbm.h      |   2 +
- .../slapd/back-ldbm/db-bdb/bdb_import.c       |  39 +----
- .../back-ldbm/db-mdb/mdb_import_threads.c     |  46 ++++-
- ldap/servers/slapd/back-ldbm/index.c          | 161 +++++++-----------
- ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c |  13 +-
- .../servers/slapd/back-ldbm/proto-back-ldbm.h |   2 +-
- ldap/servers/slapd/log.c                      |  43 +++++
- ldap/servers/slapd/slapi-private.h            |   2 +
- 10 files changed, 251 insertions(+), 144 deletions(-)
-
-diff --git a/dirsrvtests/tests/suites/indexes/regression_test.py b/dirsrvtests/tests/suites/indexes/regression_test.py
-index 71670de87..39f63e349 100644
---- a/dirsrvtests/tests/suites/indexes/regression_test.py
-+++ b/dirsrvtests/tests/suites/indexes/regression_test.py
-@@ -900,6 +900,91 @@ def test_concurrent_modifications_during_indexing(topo, homeDirectory_index_clea
-         user.delete()
- 
- 
-+def test_idl_range_limit(topo, add_some_entries):
-+    """Test nsslapd-rangelookthroughlimit and AND shortcut
-+
-+    :id: 746a5af2-d755-11f0-8b62-c85309d5c3e3
-+    :setup: Standalone Instance with some entries
-+    :steps:
-+        1. Set nsslapd-rangelookthroughlimit
-+        2. Open a new connection and bound it as an user
-+        3. Perform Range search that hit nsslapd-rangelookthroughlimit and AND shortcut
-+    :expectedresults:
-+        1. Success
-+        2. Success
-+        3. Success
-+    """
-+
-+    inst = topo.standalone
-+    added_entries = add_some_entries
-+
-+    dbconfig = LDBMConfig(inst)
-+    dbconfig.set('nsslapd-rangelookthroughlimit', "50")
-+
-+    conn = added_entries[0].bind(PW_DM)
-+
-+    entries = conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(&(objectclass=nsOrgPerson)(sn=foo)(uid>=a))")
-+    assert len(entries) == 3
-+
-+
-+def test_large_multivalued_sn_attribute(topo):
-+    """Test adding a user entry with 512 values for sn attribute, each 512 bytes
-+
-+    :id: 8f2a9b3c-e8d7-11ef-9a5f-482ae39447e5
-+    :setup: Standalone Instance
-+    :steps:
-+        1. Create a user with 512 sn values, each 512 bytes long
-+        2. Verify the user was created successfully
-+        3. Search for the user and verify all sn values are present
-+        4. Clean up the user entry
-+    :expectedresults:
-+        1. User is created successfully
-+        2. User entry exists
-+        3. All 512 sn values are present and have correct length
-+        4. User is deleted successfully
-+    """
-+
-+    inst = topo.standalone
-+    users = UserAccounts(inst, DEFAULT_SUFFIX)
-+
-+    log.info("Creating user with 512 sn values, each 512 bytes")
-+
-+    # Generate 512 unique sn values, each 512 bytes long
-+    # Use a pattern that makes each value unique but predictable
-+    sn_values = []
-+    for i in range(512):
-+        # Create a 512-byte value with unique identifier at the start
-+        value = f'sn_value_{i:04d}_' + 'x' * (512 - len(f'sn_value_{i:04d}_'))
-+        sn_values.append(value)
-+
-+    # Create the user with first sn value
-+    user_name = 'test_user_large_sn'
-+    user = users.create(properties={
-+        'uid': user_name,
-+        'cn': user_name,
-+        'sn': sn_values,
-+        'uidNumber': '99999',
-+        'gidNumber': '99999',
-+        'homeDirectory': f'/home/{user_name}'
-+    })
-+
-+    # Verify the entry was created and has all sn values
-+    log.info("Verifying all sn values are present")
-+    sn_attr_values = user.get_attr_vals_utf8('sn')
-+
-+    assert len(sn_attr_values) == 512, f"Expected 512 sn values, got {len(sn_attr_values)}"
-+
-+    # Verify each value has the correct length
-+    for idx, value in enumerate(sn_attr_values):
-+        assert len(value) == 512, f"sn value {idx} has length {len(value)}, expected 512"
-+
-+    log.info("Successfully created and verified user with 512 sn values of 512 bytes each")
-+
-+    # Clean up
-+    user.delete()
-+    log.info("User entry deleted successfully")
-+
-+
- if __name__ == "__main__":
-     # Run isolated
-     # -s for DEBUG mode
-diff --git a/ldap/servers/slapd/back-ldbm/attrcrypt.h b/ldap/servers/slapd/back-ldbm/attrcrypt.h
-index d653ba951..dcbea80fe 100644
---- a/ldap/servers/slapd/back-ldbm/attrcrypt.h
-+++ b/ldap/servers/slapd/back-ldbm/attrcrypt.h
-@@ -10,7 +10,7 @@
- #include <config.h>
- #endif
- 
--/* Private tructures and #defines used in the attribute encryption code. */
-+/* Private structures and #defines used in the attribute encryption code. */
- 
- #ifndef _ATTRCRYPT_H_
- #define _ATTRCRYPT_H_
-diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
-index e23e7ff43..92aa1ddbb 100644
---- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
-+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
-@@ -104,6 +104,8 @@ typedef unsigned short u_int16_t;
-  */
- #define BE_CHANGELOG_FILE     "replication_changelog"
- 
-+#define INDEX_KEY_LENGTH(lenval,lenprefix)  (lenval+lenprefix+2)
-+
- #define BDB_IMPL              "bdb"
- #define BDB_BACKEND           "libback-ldbm" /* This backend plugin */
- #define BDB_NEWIDL            "newidl"       /* new idl format */
-diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
-index a6cb10aec..489433801 100644
---- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
-+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
-@@ -75,9 +75,9 @@ static IDList *bdb_idl_union_allids(backend *be, struct attrinfo *ai, IDList *a,
- #define DEBUG_SUBCOUNT_MSG(msg, ...) { debug_subcount(__FUNCTION__, __LINE__, (msg), __VA_ARGS__); }
- #define DUMP_SUBCOUNT_KEY(msg, key, ret) { debug_subcount(__FUNCTION__, __LINE__, "ret=%d size=%u ulen=%u doff=%u dlen=%u", \
-                                                ret, (key).size, (key).ulen, (key).doff, (key).dlen); \
--                                           if (ret == 0) hexadump(msg, (key).data, 0, (key).size); \
-+                                           if (ret == 0) slapi_log_hexadump(SLAPI_LOG_INFO, msg, (key).data, (key).size); \
-                                            else if (ret == DB_BUFFER_SMALL) \
--                                                hexadump(msg, (key).data, 0, (key).ulen); }
-+                                                slapi_log_hexadump(SLAPI_LOG_INFO, msg, (key).data, (key).ulen); }
- 
- static void
- debug_subcount(const char *funcname, int line, char *msg, ...)
-@@ -90,41 +90,6 @@ debug_subcount(const char *funcname, int line, char *msg, ...)
-     slapi_log_err(SLAPI_LOG_INFO, (char*)funcname, "DEBUG SUBCOUNT [%d] %s\n", line, buff);
- }
- 
--/*
-- * Dump a memory buffer in hexa and ascii in error log
-- *
-- * addr - The memory buffer address.
-- * len - The memory buffer lenght.
-- */
--static void
--hexadump(char *msg, const void *addr, size_t offset, size_t len)
--{
--#define  HEXADUMP_TAB 4
--/* 4 characters per bytes:  2 hexa digits, 1 space and the ascii  */
--#define  HEXADUMP_BUF_SIZE (4*16+HEXADUMP_TAB)
--    char hexdigit[] = "0123456789ABCDEF";
--
--    const unsigned char *pt = addr;
--    char buff[HEXADUMP_BUF_SIZE+1];
--    memset (buff, ' ', HEXADUMP_BUF_SIZE);
--    buff[HEXADUMP_BUF_SIZE] = '\0';
--    while (len > 0) {
--        int dpl;
--        for (dpl = 0; dpl < 16 && len>0; dpl++, len--) {
--           buff[3*dpl] = hexdigit[((*pt) >> 4) & 0xf];
--           buff[3*dpl+1] = hexdigit[(*pt) & 0xf];
--           buff[3*16+HEXADUMP_TAB+dpl] = (*pt>=0x20 && *pt<0x7f) ? *pt : '.';
--           pt++;
--        }
--        for (;dpl < 16; dpl++) {
--           buff[3*dpl] = ' ';
--           buff[3*dpl+1] = ' ';
--           buff[3*16+HEXADUMP_TAB+dpl] = ' ';
--        }
--        slapi_log_err(SLAPI_LOG_INFO, msg, "[0x%08lx]  %s\n", offset, buff);
--        offset += 16;
--    }
--}
- #else
- #define DEBUG_SUBCOUNT_MSG(msg, ...)
- #define DUMP_SUBCOUNT_KEY(msg, key, ret)
-diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
-index 2270abd69..bfb902c02 100644
---- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
-+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
-@@ -1122,6 +1122,21 @@ dbmdb_import_entry_info_by_backentry(mdb_privdb_t *db, BulkQueueData_t *bqdata,
-     return dnrc;
- }
- 
-+/* Log wqelmt details */
-+void
-+log_wqelmt(int loglvl, char *fname, WorkerQueueData_t *wqelmt)
-+{
-+    if (wqelmt->dn) {
-+        slapi_log_err(loglvl, fname, "log_wqelmt: dn=%s\n", wqelmt->dn);
-+    }
-+    if (wqelmt->filename && wqelmt->lineno) {
-+        slapi_log_err(loglvl, fname, "log_wqelmt: ldif=%s[%d]\n", wqelmt->filename, wqelmt->lineno);
-+    }
-+    if (wqelmt->data) {
-+        size_t len = wqelmt->datalen ? wqelmt->datalen : strlen(wqelmt->data);
-+        slapi_log_hexadump(loglvl, "log_wqelmt:data", wqelmt->data, len);
-+    }
-+}
- 
- /* producer thread for ldif import case:
-  * read through the given file list, parsing entries (str2entry), assigning
-@@ -1254,6 +1269,7 @@ dbmdb_import_producer(void *param)
-                 import_log_notice(job, SLAPI_LOG_ERR, "dbmdb_import_producer",
-                                   "ns_slapd software error: unexpected dbmdb_import_entry_info return code: %d.",
-                                   wqelmt.dnrc);
-+                log_wqelmt(SLAPI_LOG_ERR, "dbmdb_import_producer", &wqelmt);
-                 abort();
-             case DNRC_OK:
-             case DNRC_SUFFIX:
-@@ -1757,6 +1773,7 @@ dbmdb_index_producer(void *param)
-                 import_log_notice(job, SLAPI_LOG_ERR, "dbmdb_index_producer",
-                                   "ns_slapd software error: unexpected dbmdb_import_entry_info return code: %d.",
-                                   tmpslot.dnrc);
-+                log_wqelmt(SLAPI_LOG_ERR, "dbmdb_index_producer", &tmpslot);
-                 abort();
-             case DNRC_OK:
-             case DNRC_SUFFIX:
-@@ -3934,10 +3951,24 @@ dbmdb_import_writer(void*param)
-             if (!txn) {
-                 MDB_STAT_STEP(stats, MDB_STAT_TXNSTART);
-                 rc = TXN_BEGIN(ctx->ctx->env, NULL, 0, &txn);
-+                if (rc) {
-+                    slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
-+                                  "Failed to begin a txn. Error is 0x%x: %s.\n",
-+                                  rc, mdb_strerror(rc));
-+                }
-             }
-             if (!rc) {
-                 MDB_STAT_STEP(stats, MDB_STAT_WRITE);
-                 rc = MDB_PUT(txn, slot->dbi->dbi, &slot->key, &slot->data, 0);
-+                if (rc) {
-+                    slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
-+                                  "Failed to write record in dbi %s. Error is 0x%x: %s.\n",
-+                                  slot->dbi->dbname, rc, mdb_strerror(rc));
-+                    slapi_log_hexadump(SLAPI_LOG_ERR, "dbmdb_import_writer:key", 
-+                                       slot->key.mv_data, slot->key.mv_size);
-+                    slapi_log_hexadump(SLAPI_LOG_ERR, "dbmdb_import_writer:data", 
-+                                       slot->data.mv_data, slot->data.mv_size);
-+                }
-             }
-             MDB_STAT_STEP(stats, MDB_STAT_RUN);
-             nextslot = slot->next;
-@@ -3951,6 +3982,9 @@ dbmdb_import_writer(void*param)
-             rc = TXN_COMMIT(txn);
-             MDB_STAT_STEP(stats, MDB_STAT_RUN);
-             if (rc) {
-+                slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
-+                              "Failed to commit the txn. Error is 0x%x: %s.\n",
-+                              rc, mdb_strerror(rc));
-                 break;
-             }
-             count = 0;
-@@ -3963,6 +3997,10 @@ dbmdb_import_writer(void*param)
-         MDB_STAT_STEP(stats, MDB_STAT_RUN);
-         if (!rc) {
-             txn = NULL;
-+        } else {
-+            slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
-+                          "Failed to commit the txn. Error is 0x%x: %s.\n",
-+                          rc, mdb_strerror(rc));
-         }
-     }
-     if (txn) {
-@@ -3975,13 +4013,17 @@ dbmdb_import_writer(void*param)
-     if (!rc) {
-         /* Ensure that all data are written on disk */
-         rc = mdb_env_sync(ctx->ctx->env, 1);
-+        if (rc) {
-+            slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
-+                          "mdb_env_sync failed. Error is 0x%x: %s.\n",
-+                          rc, mdb_strerror(rc));
-+        }
-     }
-     MDB_STAT_END(stats);
- 
-     if (rc) {
-         slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
--                "Failed to write in the database. Error is 0x%x: %s.\n",
--                rc, mdb_strerror(rc));
-+                "Aborting import after failure.\n");
-         thread_abort(info);
-     } else {
-         char buf[200];
-diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c
-index a5004be19..c108bce3c 100644
---- a/ldap/servers/slapd/back-ldbm/index.c
-+++ b/ldap/servers/slapd/back-ldbm/index.c
-@@ -881,6 +881,67 @@ index_read(
-     return index_read_ext(be, (char *)type, indextype, val, txn, err, NULL);
- }
- 
-+/* Prepare an index key (hashed if too long, encrypted if needed from attribute value */
-+int
-+prepare_key(backend *be, struct attrinfo *a, char **buf, size_t *buflen,
-+            int flags, const char *prefix, const struct berval *bvp, dbi_val_t *key)
-+{
-+    /* Key format is [Hash?] [prefix] [val] [\0] */
-+    struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
-+    size_t plen = strlen(prefix);
-+    struct berval *hashed_bvp = NULL;
-+    struct berval *encrypted_bvp = NULL;
-+    int rc = 0;
-+
-+    /* Hash large index key if necessary */
-+    if (INDEX_KEY_LENGTH(bvp->bv_len,plen) >=  li->li_max_key_len) {
-+        rc = attrcrypt_hash_large_index_key(be, prefix, a, bvp, &hashed_bvp);
-+        if (rc) {
-+            slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
-+                          "Failed to hash large index key for %s\n", a->ai_type);
-+            return rc;
-+        } else {
-+            bvp = hashed_bvp;
-+        }
-+    }
-+
-+    /* Encrypt the index key if necessary */
-+    if (rc == 0 && a->ai_attrcrypt && (0 == (flags & BE_INDEX_DONT_ENCRYPT))) {
-+        rc = attrcrypt_encrypt_index_key(be, a, bvp, &encrypted_bvp);
-+        if (rc) {
-+            slapi_log_err(SLAPI_LOG_ERR, "addordel_values_sv",
-+                          "Failed to encrypt index key for %s\n", a->ai_type);
-+        } else {
-+            bvp = encrypted_bvp;
-+        }
-+    }
-+    if (hashed_bvp) {
-+        prefix = slapi_ch_smprintf("%c%s",HASH_PREFIX, prefix);
-+        plen++;
-+    }
-+    if (buf && buflen) {
-+        if (plen+bvp->bv_len+1 > *buflen) {
-+            *buflen = plen+bvp->bv_len+1;
-+            *buf = slapi_ch_realloc(*buf, *buflen);
-+        }
-+        dblayer_value_concat(be, key, *buf, *buflen, prefix, plen, bvp->bv_val, bvp->bv_len, "", 1);
-+    } else {
-+        dblayer_value_concat(be, key, NULL, 0, prefix, plen, bvp->bv_val, bvp->bv_len, "", 1);
-+    }
-+
-+    if (hashed_bvp) {
-+        ber_bvfree(hashed_bvp);
-+        hashed_bvp = NULL;
-+        slapi_ch_free_string((char**)&prefix);
-+    }
-+    if (encrypted_bvp) {
-+        ber_bvfree(encrypted_bvp);
-+        encrypted_bvp = NULL;
-+    }
-+    return rc;
-+}
-+
-+
- /*
-  * Extended version of index_read.
-  * The unindexed flag can be used to distinguish between a
-@@ -917,7 +978,6 @@ index_read_ext_allids(
-     struct berval *hashed_val = NULL;
-     int is_and = 0;
-     unsigned int ai_flags = 0;
--    struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
- 
-     *err = 0;
- 
-@@ -1028,36 +1088,7 @@ index_read_ext_allids(
-     }
- 
-     if (val != NULL) {
--        size_t vlen;
--        int ret = 0;
--
--        /* If necessary, hash this index key */
--        if (val->bv_len >=  li->li_max_key_len) {
--            ret = attrcrypt_hash_large_index_key(be, &prefix, ai, val, &hashed_val);
--            if (ret) {
--                slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
--                              "Failed to hash large index key for %s\n", basetype);
--                *err = DBI_RC_OTHER;
--                index_free_prefix(prefix);
--                slapi_ch_free_string(&basetmp);
--                return (NULL);
--            }
--            if (hashed_val) {
--                val = hashed_val;
--            }
--        }
--        /* If necessary, encrypt this index key */
--        ret = attrcrypt_encrypt_index_key(be, ai, val, &encrypted_val);
--        if (ret) {
--            slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
--                          "Failed to encrypt index key for %s\n", basetype);
--        }
--        if (encrypted_val) {
--            val = encrypted_val;
--        }
--        vlen = val->bv_len;
--        dblayer_value_concat(be, &key, buf, sizeof(buf),
--            prefix, strlen(prefix), val->bv_val, vlen, "", 1);
-+        (void) prepare_key(be, ai, NULL, 0, 0, prefix, val, &key);
-     } else {
-         dblayer_value_concat(be, &key, buf, sizeof(buf), prefix, strlen(prefix),
-             "", 1, NULL, 0);
-@@ -1824,6 +1855,7 @@ index_range_read(
-     return index_range_read_ext(pb, be, type, indextype, operator, val, nextval, range, txn, err, 0);
- }
- 
-+
- static int
- addordel_values_sv(
-     backend *be,
-@@ -1842,15 +1874,10 @@ addordel_values_sv(
-     int i = 0;
-     dbi_val_t key = {0};
-     dbi_txn_t *db_txn = NULL;
--    size_t plen, vlen, len;
-     char *tmpbuf = NULL;
-     size_t tmpbuflen = 0;
--    char *realbuf;
-     char *prefix = NULL;
-     const struct berval *bvp;
--    struct berval *hashed_bvp = NULL;
--    struct berval *encrypted_bvp = NULL;
--    struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
-     char *index_id = get_index_name(be, db, a);
- 
-     slapi_log_err(SLAPI_LOG_TRACE, "addordel_values_sv", "%s_values\n",
-@@ -1889,66 +1916,14 @@ addordel_values_sv(
-         return (rc);
-     }
- 
--    plen = strlen(prefix);
-     for (i = 0; vals[i] != NULL; i++) {
-         bvp = slapi_value_get_berval(vals[i]);
- 
--        /* Hash large index key if necessary */
--        if (bvp->bv_len >=  li->li_max_key_len) {
--            rc = attrcrypt_hash_large_index_key(be, &prefix, a, bvp, &hashed_bvp);
--            if (rc) {
--                slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
--                              "Failed to hash large index key for %s\n", a->ai_type);
--                break;
--            } else {
--                bvp = hashed_bvp;
--                plen = strlen(prefix);
--            }
--        }
--        /* Encrypt the index key if necessary */
--        {
--            if (a->ai_attrcrypt && (0 == (flags & BE_INDEX_DONT_ENCRYPT))) {
--                rc = attrcrypt_encrypt_index_key(be, a, bvp, &encrypted_bvp);
--                if (rc) {
--                    slapi_log_err(SLAPI_LOG_ERR, "addordel_values_sv",
--                                  "Failed to encrypt index key for %s\n", a->ai_type);
--                } else {
--                    bvp = encrypted_bvp;
--                }
--            }
-+        rc = prepare_key(be, a, &tmpbuf, &tmpbuflen, flags, prefix, bvp, &key);
-+        if (rc) {
-+            break;
-         }
- 
--        vlen = bvp->bv_len;
--        len = plen + vlen;
--
--        if (len < tmpbuflen) {
--            realbuf = tmpbuf;
--        } else {
--            tmpbuf = slapi_ch_realloc(tmpbuf, len + 1);
--            tmpbuflen = len + 1;
--            realbuf = tmpbuf;
--        }
--
--        assert(realbuf); /* For coverity */
--        memcpy(realbuf, prefix, plen);
--        memcpy(realbuf + plen, bvp->bv_val, vlen);
--        realbuf[len] = '\0';
--        /* Free the encrypted berval if necessary */
--        if (hashed_bvp) {
--            ber_bvfree(hashed_bvp);
--            hashed_bvp = NULL;
--        }
--        if (encrypted_bvp) {
--            ber_bvfree(encrypted_bvp);
--            encrypted_bvp = NULL;
--        }
--        /* should be okay to use USERMEM here because we know what
--         * the key is and it should never return a different value
--         * than the one we pass in.
--         */
--        dblayer_value_set_buffer(be, &key, realbuf, plen + vlen + 1);
--        key.ulen = tmpbuflen;
--
-         if (slapi_is_loglevel_set(LDAP_DEBUG_TRACE)) {
-             char encbuf[BUFSIZ];
- 
-@@ -1981,10 +1956,6 @@ addordel_values_sv(
-             ldbm_nasty(NASTY_MSG("addordel_values_sv"), index_id, 1130, rc);
-             break;
-         }
--        if (NULL != key.dptr && realbuf != key.dptr) { /* realloc'ed */
--            tmpbuf = key.dptr;
--            tmpbuflen = key.size;
--        }
-     }
-     index_free_prefix(prefix);
-     if (tmpbuf != NULL) {
-diff --git a/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c b/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c
-index 7bb57ca66..7cf30053c 100644
---- a/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c
-+++ b/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c
-@@ -1069,15 +1069,15 @@ attrcrypt_decrypt_index_key(backend *be,
-  *                  :     NULL - no hash or failure
-  */
- int
--attrcrypt_hash_large_index_key(backend *be, char **prefix, struct attrinfo *ai, const struct berval *in, struct berval **out)
-+attrcrypt_hash_large_index_key(backend *be, const char *prefix, struct attrinfo *ai, const struct berval *in, struct berval **out)
- {
-     int ret = 0;
-     struct berval *out_berval = NULL;
-     struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
--    char *new_prefix;
-+    size_t final_key_len = INDEX_KEY_LENGTH(in->bv_len, strlen(prefix));
- 
-     /* If the index key is too long (i.e mdb case) we must hash it */
--    if (in->bv_len >=  li->li_max_key_len) {
-+    if (final_key_len >=  li->li_max_key_len) {
-         PK11Context *c = PK11_CreateDigestContext(SEC_OID_MD5);
-         if (c != NULL) {
-             unsigned char hash[32];
-@@ -1091,16 +1091,13 @@ attrcrypt_hash_large_index_key(backend *be, char **prefix, struct attrinfo *ai,
-                 return ENOMEM;
-             }
-             slapi_log_err(SLAPI_LOG_TRACE, "attrcrypt_hash_large_index_key",
--                          "Key lenght (%lu) >= max key lenght (%lu) so key must be hashed\n", in->bv_len, li->li_max_key_len);
-+                          "Key lenght (%lu) >= max key lenght (%lu) so key must be hashed\n", final_key_len, li->li_max_key_len);
-             slapi_be_set_flag(be, SLAPI_BE_FLAG_DONT_BYPASS_FILTERTEST);
-             PK11_DigestBegin(c);
-             /* Compute hash for the key without the prefix */
-             PK11_DigestOp(c, (unsigned char *)in->bv_val, in->bv_len);
-             PK11_DigestFinal(c, hash, &hashLen, sizeof hash);
--            /* Add HASH_PREFIX before the prefix */
--            new_prefix = slapi_ch_smprintf("%c%s", HASH_PREFIX, *prefix);
--            index_free_prefix(*prefix);
--            *prefix = new_prefix;
-+
-             /* Build the key: hash value in hexa */
-             hkey = slapi_ch_malloc(1+2*sizeof hash);
-             out_berval->bv_val = hkey;
-diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
-index 30a7aa11f..c882dac7b 100644
---- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
-+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
-@@ -622,7 +622,7 @@ int attrcrypt_encrypt_entry_inplace(backend *be, const struct backentry *inout);
- int attrcrypt_encrypt_entry(backend *be, const struct backentry *in, struct backentry **out);
- int attrcrypt_encrypt_index_key(backend *be, struct attrinfo *ai, const struct berval *in, struct berval **out);
- int attrcrypt_decrypt_index_key(backend *be, struct attrinfo *ai, const struct berval *in, struct berval **out);
--int attrcrypt_hash_large_index_key(backend *be, char **prefix, struct attrinfo *ai, const struct berval *in, struct berval **out);
-+int attrcrypt_hash_large_index_key(backend *be, const char *prefix, struct attrinfo *ai, const struct berval *in, struct berval **out);
- int attrcrypt_init(ldbm_instance *li);
- int attrcrypt_cleanup_private(ldbm_instance *li);
- 
-diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
-index 80c07382a..93101494b 100644
---- a/ldap/servers/slapd/log.c
-+++ b/ldap/servers/slapd/log.c
-@@ -93,6 +93,10 @@ static int slapi_log_map[] = {
- #define FLUSH PR_TRUE
- #define NO_FLUSH PR_FALSE
- 
-+#define  HEXADUMP_TAB 4
-+/* 4 characters per bytes:  2 hexa digits, 1 space and the ascii  */
-+#define  HEXADUMP_BUF_SIZE (4*16+HEXADUMP_TAB)
-+
- /**************************************************************************
-  * PROTOTYPES
-  *************************************************************************/
-@@ -3133,6 +3137,45 @@ slapi_log_backtrace(int loglevel)
-     }
- }
- 
-+/*
-+ * Dump a memory buffer in hexa and ascii in error log
-+ *
-+ * addr - The memory buffer address.
-+ * len - The memory buffer lenght.
-+ */
-+void
-+slapi_log_hexadump(int loglevel, char *fname, const void *addr, size_t len)
-+{
-+    char hexdigit[] = "0123456789ABCDEF";
-+    const unsigned char *pt = addr;
-+    char buff[HEXADUMP_BUF_SIZE+1];
-+    size_t offset = 0;
-+
-+    if (!slapi_is_loglevel_set(loglevel)) {
-+        return;
-+    }
-+    memset (buff, ' ', HEXADUMP_BUF_SIZE);
-+    buff[HEXADUMP_BUF_SIZE] = '\0';
-+    while (len > 0) {
-+        int dpl;
-+        for (dpl = 0; dpl < 16 && len>0; dpl++, len--) {
-+           buff[3*dpl] = hexdigit[((*pt) >> 4) & 0xf];
-+           buff[3*dpl+1] = hexdigit[(*pt) & 0xf];
-+           buff[3*16+HEXADUMP_TAB+dpl] = (*pt>=0x20 && *pt<0x7f) ? *pt : '.';
-+           pt++;
-+        }
-+        for (;dpl < 16; dpl++) {
-+           buff[3*dpl] = ' ';
-+           buff[3*dpl+1] = ' ';
-+           buff[3*16+HEXADUMP_TAB+dpl] = ' ';
-+        }
-+        slapi_log_err(loglevel, fname, "[0x%08lx]  %s\n", offset, buff);
-+        offset += 16;
-+    }
-+}
-+
-+
-+
- /******************************************************************************
- * write in the access log
- ******************************************************************************/
-diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
-index 72f4cd6f0..2da37ff6e 100644
---- a/ldap/servers/slapd/slapi-private.h
-+++ b/ldap/servers/slapd/slapi-private.h
-@@ -1527,6 +1527,8 @@ void slapi_pblock_set_task_warning(Slapi_PBlock *pb, task_warning warning);
- int slapi_exists_or_add_internal(Slapi_DN *dn, const char *filter, const char *entry, const char *modifier_name);
- 
- void slapi_log_backtrace(int loglevel);
-+void slapi_log_hexadump(int loglevel, char *fname, const void *addr, size_t len);
-+
- 
- /*
-  * accesslog.c
--- 
-2.54.0
-

diff --git a/0029-Issue-7503-CVE-2026-9064-Add-a-limit-to-the-number-c.patch b/0029-Issue-7503-CVE-2026-9064-Add-a-limit-to-the-number-c.patch
deleted file mode 100644
index 3804c34..0000000
--- a/0029-Issue-7503-CVE-2026-9064-Add-a-limit-to-the-number-c.patch
+++ /dev/null
@@ -1,439 +0,0 @@
-From 7e9647f5bb5c47602f4cdf0022cf6bd22872d3ef Mon Sep 17 00:00:00 2001
-From: Mark Reynolds <mreynolds@redhat.com>
-Date: Thu, 21 May 2026 09:17:39 -0400
-Subject: [PATCH] Issue 7503 - CVE-2026-9064 - Add a limit to the number
- controls per operation
-
-Description:
-
-Security fix for CVE-2026-9064
-
-To prevent resource starvation limit the number of controls the server will
-process per operation. Reject the operation if number of controls exceeds
-the limit
-
-relates: https://github.com/389ds/389-ds-base/issues/7503
-
-References:
-    - https://access.redhat.com/security/cve/cve-2026-9064
-    - https://bugzilla.redhat.com/show_bug.cgi?id=2480093
-
-CI test assisted by: Cursor
-
-Reviewed by: jchapman & tbordaz (Thanks!!)
----
- .../suites/features/ldap_controls_test.py     | 76 ++++++++++++++++++-
- ldap/schema/01core389.ldif                    |  3 +-
- ldap/servers/slapd/control.c                  | 14 +++-
- ldap/servers/slapd/libglobs.c                 | 52 +++++++++++++
- ldap/servers/slapd/proto-slap.h               |  2 +
- ldap/servers/slapd/slap.h                     |  4 +
- .../389-console/src/lib/server/tuning.jsx     | 52 ++++++++++++-
- 7 files changed, 197 insertions(+), 6 deletions(-)
-
-diff --git a/dirsrvtests/tests/suites/features/ldap_controls_test.py b/dirsrvtests/tests/suites/features/ldap_controls_test.py
-index 0f8aa08be..59a58b21d 100644
---- a/dirsrvtests/tests/suites/features/ldap_controls_test.py
-+++ b/dirsrvtests/tests/suites/features/ldap_controls_test.py
-@@ -9,15 +9,18 @@
- import logging
- import pytest
- import ldap
-+from ldap.controls import RequestControl
- from ldap.controls.readentry import PostReadControl
- from lib389.idm.user import UserAccounts, UserAccount
--from lib389.topologies import topology_st
--from lib389._constants import DEFAULT_SUFFIX
-+from test389.topologies import topology_st
-+from lib389._constants import DEFAULT_SUFFIX, DN_DM, PASSWORD
- 
- pytestmark = pytest.mark.tier1
- 
- log = logging.getLogger(__name__)
- 
-+MANAGE_DSAIT_OID = "2.16.840.1.113730.3.4.2"
-+MAX_CONTROLS_PER_OP_ATTR = "nsslapd-maxcontrolsperop"
- 
- def test_postread_ctrl_modify(topology_st):
-     """Test PostReadControl with LDAP modify operations.
-@@ -79,6 +82,75 @@ def test_postread_ctrl_modify(topology_st):
-     user.delete()
- 
- 
-+def _make_request_controls(count):
-+    return [
-+        RequestControl(controlType=MANAGE_DSAIT_OID, criticality=False)
-+        for _ in range(count)
-+    ]
-+
-+
-+def test_bind_excessive_controls(topology_st):
-+    """Bind request control count is limited by nsslapd-maxcontrolsperop
-+
-+    :id: c3888f02-2107-4682-a50a-2189d1436233
-+    :setup: Standalone instance
-+    :steps:
-+        1. Read nsslapd-maxcontrolsperop from cn=config (default 10)
-+        2. Bind with one fewer control than the limit
-+        3. Bind with one more control than the limit
-+        4. Set nsslapd-maxcontrolsperop to 5
-+        5. Bind with 4 controls (new limit minus one)
-+        6. Bind with 6 controls (over new limit)
-+        7. Restore nsslapd-maxcontrolsperop and re-bind as Directory Manager
-+    :expectedresults:
-+        1. Config value is 10
-+        2. Bind succeeds
-+        3. Bind fails with ldap.UNWILLING_TO_PERFORM
-+        4. Success
-+        5. Bind succeeds
-+        6. Bind fails with ldap.UNWILLING_TO_PERFORM
-+        7. Success
-+    """
-+    inst = topology_st.standalone
-+    original_max = inst.config.get_attr_val_utf8(MAX_CONTROLS_PER_OP_ATTR)
-+
-+    try:
-+        max_controls = int(inst.config.get_attr_val_utf8(MAX_CONTROLS_PER_OP_ATTR))
-+        assert max_controls == 10
-+
-+        log.info("Bind with %d controls (limit %d, limit minus one)",
-+                 max_controls - 1, max_controls)
-+        inst.simple_bind_s(DN_DM, PASSWORD,
-+                           serverctrls=_make_request_controls(max_controls - 1))
-+
-+        log.info("Bind with %d controls (limit %d plus one)",
-+                 max_controls + 1, max_controls)
-+        with pytest.raises(ldap.UNWILLING_TO_PERFORM):
-+            inst.simple_bind_s(DN_DM, PASSWORD,
-+                               serverctrls=_make_request_controls(max_controls + 1))
-+        inst.simple_bind_s(DN_DM, PASSWORD)
-+
-+        lowered_max = 5
-+        log.info("Set %s to %d", MAX_CONTROLS_PER_OP_ATTR, lowered_max)
-+        inst.config.set(MAX_CONTROLS_PER_OP_ATTR, str(lowered_max))
-+        assert int(inst.config.get_attr_val_utf8(MAX_CONTROLS_PER_OP_ATTR)) == lowered_max
-+
-+        log.info("Bind with %d controls (lowered limit %d, limit minus one)",
-+                 lowered_max - 1, lowered_max)
-+        inst.simple_bind_s(DN_DM, PASSWORD,
-+                           serverctrls=_make_request_controls(lowered_max - 1))
-+
-+        log.info("Bind with %d controls (lowered limit %d plus one)",
-+                 lowered_max + 1, lowered_max)
-+        with pytest.raises(ldap.UNWILLING_TO_PERFORM):
-+            inst.simple_bind_s(DN_DM, PASSWORD,
-+                               serverctrls=_make_request_controls(lowered_max + 1))
-+    finally:
-+        log.info("Restore %s to %s", MAX_CONTROLS_PER_OP_ATTR, original_max)
-+        inst.config.set(MAX_CONTROLS_PER_OP_ATTR, original_max)
-+        inst.simple_bind_s(DN_DM, PASSWORD)
-+
-+
- if __name__ == '__main__':
-     CURRENT_FILE = __file__
-     pytest.main(["-s", "-v", CURRENT_FILE])
-diff --git a/ldap/schema/01core389.ldif b/ldap/schema/01core389.ldif
-index bfe8259f8..7e2d1ac44 100644
---- a/ldap/schema/01core389.ldif
-+++ b/ldap/schema/01core389.ldif
-@@ -5,7 +5,7 @@
- # All rights reserved.
- #
- # License: GPL (version 3 or any later version).
--# See LICENSE for details. 
-+# See LICENSE for details.
- # END COPYRIGHT BLOCK
- #
- #
-@@ -333,6 +333,7 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2392 NAME 'nsslapd-return-original-entry
- attributeTypes: ( 2.16.840.1.113730.3.1.2393 NAME 'nsslapd-auditlog-display-attrs' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' )
- attributeTypes: ( 2.16.840.1.113730.3.1.2398 NAME 'nsslapd-haproxy-trusted-ip' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN '389 Directory Server' )
- attributeTypes: ( 2.16.840.1.113730.3.1.2400 NAME 'nsslapd-pwdPBKDF2NumIterations' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'Directory Server' )
-+attributeTypes: ( 2.16.840.1.113730.3.1.2402 NAME 'nsslapd-maxcontrolsperop' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN '389 Directory Server' )
- #
- # objectclasses
- #
-diff --git a/ldap/servers/slapd/control.c b/ldap/servers/slapd/control.c
-index d661dc6e1..9373c9f70 100644
---- a/ldap/servers/slapd/control.c
-+++ b/ldap/servers/slapd/control.c
-@@ -302,7 +302,7 @@ get_ldapmessage_controls_ext(
-     ber_tag_t tag;
-     /* ber_len_t is uint, cannot be -1 */
-     ber_len_t len = LBER_ERROR;
--    int rc, maxcontrols, curcontrols;
-+    int rc, maxcontrols, curcontrols, maxcontrols_per_op;
-     char *last;
-     int managedsait, pwpolicy_ctrl;
-     Connection *pb_conn = NULL;
-@@ -379,11 +379,21 @@ get_ldapmessage_controls_ext(
-         return (LDAP_PROTOCOL_ERROR);
-     }
- 
-+    maxcontrols_per_op = config_get_maxcontrolsperop();
-     maxcontrols = curcontrols = 0;
-     for (tag = ber_first_element(ber, &len, &last);
-          tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET;
--         tag = ber_next_element(ber, &len, last)) {
-+         tag = ber_next_element(ber, &len, last))
-+    {
-         len = -1; /* reset */
-+        if (curcontrols >= maxcontrols_per_op) {
-+            slapi_log_err(SLAPI_LOG_ERR, "get_ldapmessage_controls_ext",
-+                          "Too many controls in LDAP request (max %d)\n",
-+                          maxcontrols_per_op);
-+            rc = LDAP_UNWILLING_TO_PERFORM;
-+            goto free_and_return;
-+        }
-+
-         if (curcontrols >= maxcontrols - 1) {
- #define CONTROL_GRABSIZE 6
-             maxcontrols += CONTROL_GRABSIZE;
-diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
-index 887ae9a9d..ecf736ef6 100644
---- a/ldap/servers/slapd/libglobs.c
-+++ b/ldap/servers/slapd/libglobs.c
-@@ -1466,6 +1466,11 @@ static struct config_get_and_set
-      NULL, 0,
-      (void **)&global_slapdFrontendConfig.return_orig_dn,
-      CONFIG_ON_OFF, (ConfigGetFunc)config_get_return_orig_dn, &init_return_orig_dn, NULL},
-+    {CONFIG_MAXCONTROLS_PER_OP_ATTRIBUTE, config_set_maxcontrolsperop,
-+     NULL, 0,
-+     (void **)&global_slapdFrontendConfig.maxcontrols_per_op,
-+     CONFIG_INT, (ConfigGetFunc)config_get_maxcontrolsperop,
-+     SLAPD_DEFAULT_MAXCONTROLS_PER_OP_STR, NULL},
-     /* End config */
-     };
- 
-@@ -2041,6 +2046,7 @@ FrontendConfig_init(void)
-     init_cn_uses_dn_syntax_in_dns = cfg->cn_uses_dn_syntax_in_dns = LDAP_OFF;
-     init_global_backend_local = LDAP_OFF;
-     cfg->maxsimplepaged_per_conn = SLAPD_DEFAULT_MAXSIMPLEPAGED_PER_CONN;
-+    cfg->maxcontrols_per_op = SLAPD_DEFAULT_MAXCONTROLS_PER_OP;
-     cfg->maxbersize = SLAPD_DEFAULT_MAXBERSIZE;
-     cfg->logging_backend = slapi_ch_strdup(SLAPD_INIT_LOGGING_BACKEND_INTERNAL);
-     cfg->rootdn = slapi_ch_strdup(SLAPD_DEFAULT_DIRECTORY_MANAGER);
-@@ -10105,6 +10111,52 @@ config_get_maxsimplepaged_per_conn()
-     return retVal;
- }
- 
-+int
-+config_set_maxcontrolsperop(const char *attrname, char *value, char *errorbuf, int apply)
-+{
-+    int retVal = LDAP_SUCCESS;
-+    slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
-+    long size;
-+    char *endp;
-+
-+    if (config_value_is_null(attrname, value, errorbuf, 0)) {
-+        return LDAP_OPERATIONS_ERROR;
-+    }
-+
-+    errno = 0;
-+    size = strtol(value, &endp, 10);
-+    if (*endp != '\0' || errno == ERANGE || size < 1 || size > 1000) {
-+        slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
-+                              "(%s) value (%s) is invalid, must be at least 1 and less than 1000\n",
-+                              attrname, value);
-+        return LDAP_OPERATIONS_ERROR;
-+    }
-+
-+    if (!apply) {
-+        return retVal;
-+    }
-+
-+    CFG_LOCK_WRITE(slapdFrontendConfig);
-+
-+    slapdFrontendConfig->maxcontrols_per_op = size;
-+
-+    CFG_UNLOCK_WRITE(slapdFrontendConfig);
-+    return retVal;
-+}
-+
-+int
-+config_get_maxcontrolsperop()
-+{
-+    slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
-+    int retVal;
-+
-+    retVal = slapdFrontendConfig->maxcontrols_per_op;
-+    if (retVal == 0) {
-+        retVal = SLAPD_DEFAULT_MAXCONTROLS_PER_OP;
-+    }
-+    return retVal;
-+}
-+
- int32_t
- config_set_extract_pem(const char *attrname, char *value, char *errorbuf, int apply)
- {
-diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
-index 8a2f74836..c6482414f 100644
---- a/ldap/servers/slapd/proto-slap.h
-+++ b/ldap/servers/slapd/proto-slap.h
-@@ -426,6 +426,7 @@ int32_t config_set_maxdescriptors(const char *attrname, char *value, char *error
- int config_set_localuser(const char *attrname, char *value, char *errorbuf, int apply);
- 
- int config_set_maxsimplepaged_per_conn(const char *attrname, char *value, char *errorbuf, int apply);
-+int config_set_maxcontrolsperop(const char *attrname, char *value, char *errorbuf, int apply);
- 
- int log_external_libs_debug_set_log_fn(void);
- int log_set_backend(const char *attrname, char *value, int logtype, char *errorbuf, int apply);
-@@ -631,6 +632,7 @@ int config_get_malloc_mmap_threshold(void);
- #endif
- 
- int config_get_maxsimplepaged_per_conn(void);
-+int config_get_maxcontrolsperop(void);
- int config_get_extract_pem(void);
- 
- int32_t config_get_enable_upgrade_hash(void);
-diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
-index 1e5ad84bf..5b1d8850e 100644
---- a/ldap/servers/slapd/slap.h
-+++ b/ldap/servers/slapd/slap.h
-@@ -301,6 +301,8 @@ typedef void (*VFPV)(); /* takes undefined arguments */
- #define SLAPD_DEFAULT_MAXBERSIZE_STR "2097152"
- #define SLAPD_DEFAULT_MAXSIMPLEPAGED_PER_CONN (-1)
- #define SLAPD_DEFAULT_MAXSIMPLEPAGED_PER_CONN_STR "-1"
-+#define SLAPD_DEFAULT_MAXCONTROLS_PER_OP 10
-+#define SLAPD_DEFAULT_MAXCONTROLS_PER_OP_STR "10"
- #define SLAPD_DEFAULT_LDAPSSOTOKEN_TTL 3600
- #define SLAPD_DEFAULT_LDAPSSOTOKEN_TTL_STR "3600"
- 
-@@ -2417,6 +2419,7 @@ typedef struct _slapdEntryPoints
- #define CONFIG_CN_USES_DN_SYNTAX_IN_DNS "nsslapd-cn-uses-dn-syntax-in-dns"
- 
- #define CONFIG_MAXSIMPLEPAGED_PER_CONN_ATTRIBUTE "nsslapd-maxsimplepaged-per-conn"
-+#define CONFIG_MAXCONTROLS_PER_OP_ATTRIBUTE "nsslapd-maxcontrolsperop"
- #define CONFIG_LOGGING_BACKEND "nsslapd-logging-backend"
- 
- #define CONFIG_EXTRACT_PEM "nsslapd-extract-pemfiles"
-@@ -2749,6 +2752,7 @@ typedef struct _slapdFrontendConfig
-     slapi_onoff_t cn_uses_dn_syntax_in_dns; /* indicates the cn value in dns has dn syntax */
-     slapi_onoff_t global_backend_lock;
-     slapi_int_t maxsimplepaged_per_conn; /* max simple paged results reqs handled per connection */
-+    slapi_int_t maxcontrols_per_op;      /* max LDAP controls allowed per operation */
-     slapi_onoff_t enable_nunc_stans; /* Despite the removal of NS, we have to leave the value in
-                                       * case someone was setting it.
-                                       */
-diff --git a/src/cockpit/389-console/src/lib/server/tuning.jsx b/src/cockpit/389-console/src/lib/server/tuning.jsx
-index 5f56ff858..fe0ed9d2e 100644
---- a/src/cockpit/389-console/src/lib/server/tuning.jsx
-+++ b/src/cockpit/389-console/src/lib/server/tuning.jsx
-@@ -23,6 +23,7 @@ const tuning_attrs = [
-     'nsslapd-connection-nocanon',
-     'nsslapd-enable-turbo-mode',
-     'nsslapd-threadnumber',
-+    'nsslapd-maxthreadsperconn',
-     'nsslapd-maxdescriptors',
-     'nsslapd-timelimit',
-     'nsslapd-sizelimit',
-@@ -34,6 +35,7 @@ const tuning_attrs = [
-     'nsslapd-maxsasliosize',
-     'nsslapd-listen-backlog-size',
-     'nsslapd-max-filter-nest-level',
-+    'nsslapd-maxcontrolsperop',
-     'nsslapd-ndn-cache-max-size',
- ];
- 
-@@ -161,6 +163,7 @@ export class ServerTuning extends React.Component {
-                         'nsslapd-connection-nocanon': connNoCannon,
-                         'nsslapd-enable-turbo-mode': turboMode,
-                         'nsslapd-threadnumber': attrs['nsslapd-threadnumber'][0],
-+                        'nsslapd-maxthreadsperconn': attrs['nsslapd-maxthreadsperconn'][0],
-                         'nsslapd-maxdescriptors': attrs['nsslapd-maxdescriptors'][0],
-                         'nsslapd-timelimit': attrs['nsslapd-timelimit'][0],
-                         'nsslapd-sizelimit': attrs['nsslapd-sizelimit'][0],
-@@ -172,6 +175,7 @@ export class ServerTuning extends React.Component {
-                         'nsslapd-maxsasliosize': attrs['nsslapd-maxsasliosize'][0],
-                         'nsslapd-listen-backlog-size': attrs['nsslapd-listen-backlog-size'][0],
-                         'nsslapd-max-filter-nest-level': attrs['nsslapd-max-filter-nest-level'][0],
-+                        'nsslapd-maxcontrolsperop': attrs['nsslapd-maxcontrolsperop'][0],
-                         'nsslapd-ndn-cache-max-size': attrs['nsslapd-ndn-cache-max-size'][0],
-                         // Record original values
-                         '_nsslapd-ndn-cache-enabled': ndnEnabled,
-@@ -179,6 +183,7 @@ export class ServerTuning extends React.Component {
-                         '_nsslapd-connection-nocanon': connNoCannon,
-                         '_nsslapd-enable-turbo-mode': turboMode,
-                         '_nsslapd-threadnumber': attrs['nsslapd-threadnumber'][0],
-+                        '_nsslapd-maxthreadsperconn': attrs['nsslapd-maxthreadsperconn'][0],
-                         '_nsslapd-maxdescriptors': attrs['nsslapd-maxdescriptors'][0],
-                         '_nsslapd-timelimit': attrs['nsslapd-timelimit'][0],
-                         '_nsslapd-sizelimit': attrs['nsslapd-sizelimit'][0],
-@@ -190,6 +195,7 @@ export class ServerTuning extends React.Component {
-                         '_nsslapd-maxsasliosize': attrs['nsslapd-maxsasliosize'][0],
-                         '_nsslapd-listen-backlog-size': attrs['nsslapd-listen-backlog-size'][0],
-                         '_nsslapd-max-filter-nest-level': attrs['nsslapd-max-filter-nest-level'][0],
-+                        '_nsslapd-maxcontrolsperop': attrs['nsslapd-maxcontrolsperop'][0],
-                         '_nsslapd-ndn-cache-max-size': attrs['nsslapd-ndn-cache-max-size'][0],
-                     }, this.props.enableTree());
-                 })
-@@ -275,7 +281,7 @@ export class ServerTuning extends React.Component {
-                             <TextContent>
-                                 <Text component={TextVariants.h3}>
-                                     {_("Tuning & Limits")}
--                                    <Button 
-+                                    <Button
-                                         variant="plain"
-                                         aria-label={_("Refresh settings")}
-                                         onClick={() => {
-@@ -312,6 +318,28 @@ export class ServerTuning extends React.Component {
-                                 />
-                             </GridItem>
-                         </Grid>
-+                        <Grid
-+                            title={_("The maximum number of threads that can handle requests for a single connection (nsslapd-maxthreadsperconn).")}
-+                        >
-+                            <GridItem className="ds-label" span={3}>
-+                                {_("Max Threads Per Connection")}
-+                            </GridItem>
-+                            <GridItem span={9}>
-+                                <NumberInput
-+                                    value={this.state['nsslapd-maxthreadsperconn']}
-+                                    min={1}
-+                                    max={65535}
-+                                    onMinus={() => { this.onMinusConfig("nsslapd-maxthreadsperconn") }}
-+                                    onChange={(e) => { this.onConfigChange(e, "nsslapd-maxthreadsperconn", 1, 65535) }}
-+                                    onPlus={() => { this.onPlusConfig("nsslapd-maxthreadsperconn") }}
-+                                    inputName="input"
-+                                    inputAriaLabel="number input"
-+                                    minusBtnAriaLabel="minus"
-+                                    plusBtnAriaLabel="plus"
-+                                    widthChars={8}
-+                                />
-+                            </GridItem>
-+                        </Grid>
-                         <Grid
-                             title={_("The maximum number of seconds allocated for a search request.  Set to '-1' to disable the time limit (nsslapd-timelimit).")}
-                         >
-@@ -542,6 +570,28 @@ export class ServerTuning extends React.Component {
-                                         />
-                                     </GridItem>
-                                 </Grid>
-+                                <Grid
-+                                    title={_("The maximum number of LDAP controls allowed per operation (nsslapd-maxcontrolsperop).")}
-+                                >
-+                                    <GridItem className="ds-label" span={3}>
-+                                        {_("Maximum Controls Per Operation")}
-+                                    </GridItem>
-+                                    <GridItem span={9}>
-+                                        <NumberInput
-+                                            value={this.state['nsslapd-maxcontrolsperop']}
-+                                            min={1}
-+                                            max={1000}
-+                                            onMinus={() => { this.onMinusConfig("nsslapd-maxcontrolsperop") }}
-+                                            onChange={(e) => { this.onConfigChange(e, "nsslapd-maxcontrolsperop", 1, 0) }}
-+                                            onPlus={() => { this.onPlusConfig("nsslapd-maxcontrolsperop") }}
-+                                            inputName="input"
-+                                            inputAriaLabel="number input"
-+                                            minusBtnAriaLabel="minus"
-+                                            plusBtnAriaLabel="plus"
-+                                            widthChars={8}
-+                                        />
-+                                    </GridItem>
-+                                </Grid>
-                                 <Grid
-                                     title={_("Disable DNS reverse entries for outgoing connections (nsslapd-connection-nocanon).")}
-                                 >
--- 
-2.54.0
-

diff --git a/389-ds-base.spec b/389-ds-base.spec
index 965c8fc..8059312 100644
--- a/389-ds-base.spec
+++ b/389-ds-base.spec
@@ -1,6 +1,6 @@
 %global pkgname dirsrv
 
-# Exclude i686 bit arches
+# Exclude i686 architecture
 ExcludeArch: i686
 
 %bcond bundle_jemalloc 1
@@ -10,11 +10,7 @@ ExcludeArch: i686
 %global __provides_exclude ^libjemalloc\\.so.*$
 %endif
 
-%bcond bundle_libdb 0
-%if 0%{?rhel} >= 10
-%bcond bundle_libdb 1
-%endif
-
+%bcond bundle_libdb %{defined rhel}
 %if %{with bundle_libdb}
 %global libdb_version 5.3
 %global libdb_base_version db-%{libdb_version}.28
@@ -28,13 +24,18 @@ ExcludeArch: i686
 %endif
 %endif
 
-%bcond libbdb_ro 0
+%bcond repl_reports 0
+
 %if 0%{?fedora} >= 43
 %bcond libbdb_ro 1
+%else
+%bcond libbdb_ro 0
 %endif
 
 # This is used in certain builds to help us know if it has extra features.
 %global variant base
+%global prerel %{nil}
+
 # This enables a sanitized build.
 %bcond asan 0
 %bcond msan 0
@@ -53,21 +54,30 @@ ExcludeArch: i686
 
 %if %{with clang}
 %global toolchain clang
-%global _missing_build_ids_terminate_build 0
+%global _lto_cflags %nil
 %endif
 
 # Build cockpit plugin
+# Enabled on Fedora and EPEL community builds.
+# Disabled on RHEL/CentOS Stream unless RHDS (distribution contains "dsrv").
+%if 0%{?rhel} && !0%{?epel}
+%bcond cockpit %(echo "%{?distribution}" | grep -q dsrv && echo 1 || echo 0)
+%else
 %bcond cockpit 1
+%endif
 
-# fedora 15 and later uses tmpfiles.d
-# otherwise, comment this out
-%{!?with_tmpfiles_d: %global with_tmpfiles_d %{_sysconfdir}/tmpfiles.d}
+# HIBP password breach checking
+%bcond hibp 1
+
+%bcond usdt 1
 
 # systemd support
 %global groupname %{pkgname}.target
 
 # Filter argparse-manpage from autogenerated package Requires
 %global __requires_exclude ^python.*argparse-manpage
+# Filter perl auto-dep from jemalloc's jeprof profiler script
+%global __requires_exclude_from %{_libdir}/%{pkgname}/bin/jeprof
 
 # Force to require nss version greater or equal as the version available at the build time
 # See bz1986327
@@ -75,24 +85,21 @@ ExcludeArch: i686
 
 Summary:          389 Directory Server (%{variant})
 Name:             389-ds-base
-Version:          3.1.4
+Version:          3.1.5%{?prerel}
 Release:          %{autorelease -n %{?with_asan:-e asan}}%{?dist}
-License:          GPL-3.0-or-later WITH GPL-3.0-389-ds-base-exception AND (Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND (Apache-2.0 OR LGPL-2.1-or-later OR MIT) AND (Apache-2.0 OR MIT) AND (CC-BY-4.0 AND MIT) AND (MIT OR Apache-2.0) AND Unicode-3.0 AND (MIT OR CC0-1.0) AND (MIT OR Unlicense) AND 0BSD AND Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND ISC AND MIT AND MIT AND ISC AND MPL-2.0 AND PSF-2.0 AND Zlib
+License:          GPL-3.0-or-later WITH GPL-3.0-389-ds-base-exception AND (Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND (Apache-2.0 OR LGPL-2.1-or-later OR MIT) AND (Apache-2.0 OR MIT) AND (Apache-2.0 OR MIT) AND Unicode-3.0 AND (CC-BY-4.0 AND MIT) AND (MIT OR Unlicense) AND 0BSD AND Apache-2.0 AND BSD-3-Clause AND ISC AND MIT AND MIT AND ISC AND MPL-2.0 AND Zlib
 URL:              https://www.port389.org
-Obsoletes:        %{name}-legacy-tools < 1.4.4.6
-Obsoletes:        %{name}-legacy-tools-debuginfo < 1.4.4.6
-Provides:         ldif2ldbm >= 0
 
 ##### Bundled cargo crates list - START #####
 Provides:  bundled(crate(allocator-api2)) = 0.2.21
 Provides:  bundled(crate(anyhow)) = 1.0.102
 Provides:  bundled(crate(atty)) = 0.2.14
-Provides:  bundled(crate(autocfg)) = 1.5.1
-Provides:  bundled(crate(base64)) = 0.13.1
-Provides:  bundled(crate(bitflags)) = 2.12.1
+Provides:  bundled(crate(autocfg)) = 1.5.0
+Provides:  bundled(crate(base64)) = 0.23.0
+Provides:  bundled(crate(bitflags)) = 2.11.0
 Provides:  bundled(crate(byteorder)) = 1.5.0
 Provides:  bundled(crate(cbindgen)) = 0.26.0
-Provides:  bundled(crate(cc)) = 1.2.63
+Provides:  bundled(crate(cc)) = 1.2.56
 Provides:  bundled(crate(cfg-if)) = 1.0.4
 Provides:  bundled(crate(clap)) = 3.2.25
 Provides:  bundled(crate(clap_lex)) = 0.2.4
@@ -102,55 +109,55 @@ Provides:  bundled(crate(crossbeam-queue)) = 0.3.12
 Provides:  bundled(crate(crossbeam-utils)) = 0.8.21
 Provides:  bundled(crate(equivalent)) = 1.0.2
 Provides:  bundled(crate(errno)) = 0.3.14
-Provides:  bundled(crate(fastrand)) = 2.4.1
+Provides:  bundled(crate(fastrand)) = 2.3.0
 Provides:  bundled(crate(fernet)) = 0.1.4
 Provides:  bundled(crate(find-msvc-tools)) = 0.1.9
 Provides:  bundled(crate(foldhash)) = 0.2.0
 Provides:  bundled(crate(foreign-types)) = 0.3.2
 Provides:  bundled(crate(foreign-types-shared)) = 0.1.1
-Provides:  bundled(crate(getrandom)) = 0.4.2
-Provides:  bundled(crate(hashbrown)) = 0.17.1
+Provides:  bundled(crate(getrandom)) = 0.4.1
+Provides:  bundled(crate(hashbrown)) = 0.16.1
 Provides:  bundled(crate(heck)) = 0.5.0
 Provides:  bundled(crate(hermit-abi)) = 0.1.19
 Provides:  bundled(crate(id-arena)) = 2.3.0
-Provides:  bundled(crate(indexmap)) = 2.14.0
-Provides:  bundled(crate(itoa)) = 1.0.18
+Provides:  bundled(crate(indexmap)) = 2.13.0
+Provides:  bundled(crate(itoa)) = 1.0.17
 Provides:  bundled(crate(jobserver)) = 0.1.34
 Provides:  bundled(crate(leb128fmt)) = 0.1.0
-Provides:  bundled(crate(libc)) = 0.2.186
-Provides:  bundled(crate(linux-raw-sys)) = 0.12.1
-Provides:  bundled(crate(log)) = 0.4.31
-Provides:  bundled(crate(lru)) = 0.16.4
-Provides:  bundled(crate(memchr)) = 2.8.1
-Provides:  bundled(crate(once_cell)) = 1.21.4
-Provides:  bundled(crate(openssl)) = 0.10.80
+Provides:  bundled(crate(libc)) = 0.2.182
+Provides:  bundled(crate(linux-raw-sys)) = 0.11.0
+Provides:  bundled(crate(log)) = 0.4.29
+Provides:  bundled(crate(lru)) = 0.16.3
+Provides:  bundled(crate(memchr)) = 2.8.0
+Provides:  bundled(crate(once_cell)) = 1.21.3
+Provides:  bundled(crate(openssl)) = 0.10.75
 Provides:  bundled(crate(openssl-macros)) = 0.1.1
-Provides:  bundled(crate(openssl-sys)) = 0.9.116
+Provides:  bundled(crate(openssl-sys)) = 0.9.111
 Provides:  bundled(crate(os_str_bytes)) = 6.6.1
 Provides:  bundled(crate(paste)) = 0.1.18
 Provides:  bundled(crate(paste-impl)) = 0.1.18
-Provides:  bundled(crate(pin-project-lite)) = 0.2.17
-Provides:  bundled(crate(pkg-config)) = 0.3.33
+Provides:  bundled(crate(pin-project-lite)) = 0.2.16
+Provides:  bundled(crate(pkg-config)) = 0.3.32
 Provides:  bundled(crate(prettyplease)) = 0.2.37
 Provides:  bundled(crate(proc-macro-hack)) = 0.5.20+deprecated
 Provides:  bundled(crate(proc-macro2)) = 1.0.106
-Provides:  bundled(crate(quote)) = 1.0.45
-Provides:  bundled(crate(r-efi)) = 6.0.0
-Provides:  bundled(crate(rustix)) = 1.1.4
-Provides:  bundled(crate(semver)) = 1.0.28
+Provides:  bundled(crate(quote)) = 1.0.44
+Provides:  bundled(crate(r-efi)) = 5.3.0
+Provides:  bundled(crate(rustix)) = 1.1.3
+Provides:  bundled(crate(semver)) = 1.0.27
 Provides:  bundled(crate(serde)) = 1.0.228
 Provides:  bundled(crate(serde_core)) = 1.0.228
 Provides:  bundled(crate(serde_derive)) = 1.0.228
-Provides:  bundled(crate(serde_json)) = 1.0.150
-Provides:  bundled(crate(shlex)) = 2.0.1
+Provides:  bundled(crate(serde_json)) = 1.0.149
+Provides:  bundled(crate(shlex)) = 1.3.0
 Provides:  bundled(crate(smallvec)) = 1.15.1
 Provides:  bundled(crate(sptr)) = 0.3.2
 Provides:  bundled(crate(strsim)) = 0.10.0
 Provides:  bundled(crate(syn)) = 2.0.117
-Provides:  bundled(crate(tempfile)) = 3.27.0
+Provides:  bundled(crate(tempfile)) = 3.25.0
 Provides:  bundled(crate(termcolor)) = 1.4.1
 Provides:  bundled(crate(textwrap)) = 0.16.2
-Provides:  bundled(crate(tokio)) = 1.52.3
+Provides:  bundled(crate(tokio)) = 1.49.0
 Provides:  bundled(crate(toml)) = 0.5.11
 Provides:  bundled(crate(tracing)) = 0.1.44
 Provides:  bundled(crate(tracing-attributes)) = 0.1.31
@@ -160,7 +167,7 @@ Provides:  bundled(crate(unicode-xid)) = 0.2.6
 Provides:  bundled(crate(uuid)) = 0.8.2
 Provides:  bundled(crate(vcpkg)) = 0.2.15
 Provides:  bundled(crate(wasi)) = 0.11.1+wasi_snapshot_preview1
-Provides:  bundled(crate(wasip2)) = 1.0.3+wasi_0.2.9
+Provides:  bundled(crate(wasip2)) = 1.0.2+wasi_0.2.9
 Provides:  bundled(crate(wasip3)) = 0.4.0+wasi_0.3.0_rc_2026_01_06
 Provides:  bundled(crate(wasm-encoder)) = 0.244.0
 Provides:  bundled(crate(wasm-metadata)) = 0.244.0
@@ -171,7 +178,7 @@ Provides:  bundled(crate(winapi-util)) = 0.1.11
 Provides:  bundled(crate(winapi-x86_64-pc-windows-gnu)) = 0.4.0
 Provides:  bundled(crate(windows-link)) = 0.2.1
 Provides:  bundled(crate(windows-sys)) = 0.61.2
-Provides:  bundled(crate(wit-bindgen)) = 0.57.1
+Provides:  bundled(crate(wit-bindgen)) = 0.51.0
 Provides:  bundled(crate(wit-bindgen-core)) = 0.51.0
 Provides:  bundled(crate(wit-bindgen-rust)) = 0.51.0
 Provides:  bundled(crate(wit-bindgen-rust-macro)) = 0.51.0
@@ -180,20 +187,10 @@ Provides:  bundled(crate(wit-parser)) = 0.244.0
 Provides:  bundled(crate(zeroize)) = 1.8.2
 Provides:  bundled(crate(zeroize_derive)) = 1.4.3
 Provides:  bundled(crate(zmij)) = 1.0.21
-Provides:  bundled(npm(@eslint-community/eslint-utils)) = 4.4.1
-Provides:  bundled(npm(@eslint-community/regexpp)) = 4.12.1
-Provides:  bundled(npm(@eslint/eslintrc)) = 2.1.4
-Provides:  bundled(npm(@eslint/js)) = 8.57.1
 Provides:  bundled(npm(@fortawesome/fontawesome-common-types)) = 0.2.36
 Provides:  bundled(npm(@fortawesome/fontawesome-svg-core)) = 1.2.36
 Provides:  bundled(npm(@fortawesome/free-solid-svg-icons)) = 5.15.4
 Provides:  bundled(npm(@fortawesome/react-fontawesome)) = 0.1.19
-Provides:  bundled(npm(@humanwhocodes/config-array)) = 0.13.0
-Provides:  bundled(npm(@humanwhocodes/module-importer)) = 1.0.1
-Provides:  bundled(npm(@humanwhocodes/object-schema)) = 2.0.3
-Provides:  bundled(npm(@nodelib/fs.scandir)) = 2.1.5
-Provides:  bundled(npm(@nodelib/fs.stat)) = 2.0.5
-Provides:  bundled(npm(@nodelib/fs.walk)) = 1.2.8
 Provides:  bundled(npm(@patternfly/patternfly)) = 5.4.1
 Provides:  bundled(npm(@patternfly/react-charts)) = 7.4.3
 Provides:  bundled(npm(@patternfly/react-core)) = 5.4.1
@@ -202,39 +199,25 @@ Provides:  bundled(npm(@patternfly/react-log-viewer)) = 5.3.0
 Provides:  bundled(npm(@patternfly/react-styles)) = 5.4.0
 Provides:  bundled(npm(@patternfly/react-table)) = 5.4.1
 Provides:  bundled(npm(@patternfly/react-tokens)) = 5.4.0
-Provides:  bundled(npm(@types/d3-array)) = 3.2.1
+Provides:  bundled(npm(@types/d3-array)) = 3.2.2
 Provides:  bundled(npm(@types/d3-color)) = 3.1.3
 Provides:  bundled(npm(@types/d3-ease)) = 3.0.2
 Provides:  bundled(npm(@types/d3-interpolate)) = 3.0.4
-Provides:  bundled(npm(@types/d3-path)) = 3.1.0
-Provides:  bundled(npm(@types/d3-scale)) = 4.0.8
-Provides:  bundled(npm(@types/d3-shape)) = 3.1.6
-Provides:  bundled(npm(@types/d3-time)) = 3.0.3
+Provides:  bundled(npm(@types/d3-path)) = 3.1.1
+Provides:  bundled(npm(@types/d3-scale)) = 4.0.9
+Provides:  bundled(npm(@types/d3-shape)) = 3.1.8
+Provides:  bundled(npm(@types/d3-time)) = 3.0.4
 Provides:  bundled(npm(@types/d3-timer)) = 3.0.2
-Provides:  bundled(npm(@ungap/structured-clone)) = 1.2.0
 Provides:  bundled(npm(@xterm/addon-canvas)) = 0.7.0
 Provides:  bundled(npm(@xterm/xterm)) = 5.5.0
-Provides:  bundled(npm(acorn)) = 8.14.0
-Provides:  bundled(npm(acorn-jsx)) = 5.3.2
-Provides:  bundled(npm(ajv)) = 6.15.0
-Provides:  bundled(npm(ansi-regex)) = 5.0.1
-Provides:  bundled(npm(ansi-styles)) = 4.3.0
-Provides:  bundled(npm(argparse)) = 2.0.1
-Provides:  bundled(npm(attr-accept)) = 2.2.4
+Provides:  bundled(npm(argparse)) = 1.0.10
+Provides:  bundled(npm(attr-accept)) = 2.2.5
 Provides:  bundled(npm(autolinker)) = 3.16.2
-Provides:  bundled(npm(balanced-match)) = 1.0.2
-Provides:  bundled(npm(brace-expansion)) = 1.1.15
-Provides:  bundled(npm(callsites)) = 3.1.0
-Provides:  bundled(npm(chalk)) = 4.1.2
-Provides:  bundled(npm(color-convert)) = 2.0.1
-Provides:  bundled(npm(color-name)) = 1.1.4
-Provides:  bundled(npm(concat-map)) = 0.0.1
 Provides:  bundled(npm(core-util-is)) = 1.0.3
-Provides:  bundled(npm(cross-spawn)) = 7.0.6
 Provides:  bundled(npm(d3-array)) = 3.2.4
 Provides:  bundled(npm(d3-color)) = 3.1.0
 Provides:  bundled(npm(d3-ease)) = 3.0.1
-Provides:  bundled(npm(d3-format)) = 3.1.0
+Provides:  bundled(npm(d3-format)) = 3.1.2
 Provides:  bundled(npm(d3-interpolate)) = 3.0.1
 Provides:  bundled(npm(d3-path)) = 3.1.0
 Provides:  bundled(npm(d3-scale)) = 4.0.2
@@ -242,145 +225,72 @@ Provides:  bundled(npm(d3-shape)) = 3.2.0
 Provides:  bundled(npm(d3-time)) = 3.1.0
 Provides:  bundled(npm(d3-time-format)) = 4.1.0
 Provides:  bundled(npm(d3-timer)) = 3.0.1
-Provides:  bundled(npm(debug)) = 4.3.7
-Provides:  bundled(npm(deep-is)) = 0.1.4
 Provides:  bundled(npm(delaunator)) = 4.0.1
 Provides:  bundled(npm(delaunay-find)) = 0.0.6
 Provides:  bundled(npm(dequal)) = 2.0.3
-Provides:  bundled(npm(doctrine)) = 3.0.0
 Provides:  bundled(npm(encoding)) = 0.1.13
-Provides:  bundled(npm(escape-string-regexp)) = 4.0.0
-Provides:  bundled(npm(eslint)) = 8.57.1
-Provides:  bundled(npm(eslint-plugin-react-hooks)) = 4.6.2
-Provides:  bundled(npm(eslint-scope)) = 7.2.2
-Provides:  bundled(npm(eslint-visitor-keys)) = 3.4.3
-Provides:  bundled(npm(espree)) = 9.6.1
-Provides:  bundled(npm(esquery)) = 1.6.0
-Provides:  bundled(npm(esrecurse)) = 4.3.0
-Provides:  bundled(npm(estraverse)) = 5.3.0
-Provides:  bundled(npm(esutils)) = 2.0.3
-Provides:  bundled(npm(fast-deep-equal)) = 3.1.3
-Provides:  bundled(npm(fast-json-stable-stringify)) = 2.1.0
-Provides:  bundled(npm(fast-levenshtein)) = 2.0.6
-Provides:  bundled(npm(fastq)) = 1.17.1
-Provides:  bundled(npm(file-entry-cache)) = 6.0.1
-Provides:  bundled(npm(file-selector)) = 2.1.0
-Provides:  bundled(npm(find-up)) = 5.0.0
-Provides:  bundled(npm(flat-cache)) = 3.2.0
-Provides:  bundled(npm(flatted)) = 3.4.2
+Provides:  bundled(npm(file-selector)) = 2.1.2
 Provides:  bundled(npm(focus-trap)) = 7.5.4
-Provides:  bundled(npm(fs.realpath)) = 1.0.0
 Provides:  bundled(npm(gettext-parser)) = 2.1.0
-Provides:  bundled(npm(glob)) = 7.2.3
-Provides:  bundled(npm(glob-parent)) = 6.0.2
-Provides:  bundled(npm(globals)) = 13.24.0
-Provides:  bundled(npm(graphemer)) = 1.4.0
-Provides:  bundled(npm(has-flag)) = 4.0.0
 Provides:  bundled(npm(hoist-non-react-statics)) = 3.3.2
 Provides:  bundled(npm(iconv-lite)) = 0.6.3
-Provides:  bundled(npm(ignore)) = 5.3.2
-Provides:  bundled(npm(import-fresh)) = 3.3.0
-Provides:  bundled(npm(imurmurhash)) = 0.1.4
-Provides:  bundled(npm(inflight)) = 1.0.6
 Provides:  bundled(npm(inherits)) = 2.0.4
 Provides:  bundled(npm(internmap)) = 2.0.3
-Provides:  bundled(npm(is-extglob)) = 2.1.1
-Provides:  bundled(npm(is-glob)) = 4.0.3
-Provides:  bundled(npm(is-path-inside)) = 3.0.3
 Provides:  bundled(npm(isarray)) = 1.0.0
-Provides:  bundled(npm(isexe)) = 2.0.0
 Provides:  bundled(npm(js-sha1)) = 0.7.0
 Provides:  bundled(npm(js-sha256)) = 0.11.0
 Provides:  bundled(npm(js-tokens)) = 4.0.0
-Provides:  bundled(npm(js-yaml)) = 4.1.1
-Provides:  bundled(npm(json-buffer)) = 3.0.1
-Provides:  bundled(npm(json-schema-traverse)) = 0.4.1
 Provides:  bundled(npm(json-stable-stringify-without-jsonify)) = 1.0.1
 Provides:  bundled(npm(json-stringify-safe)) = 5.0.1
-Provides:  bundled(npm(keyv)) = 4.5.4
-Provides:  bundled(npm(levn)) = 0.4.1
-Provides:  bundled(npm(locate-path)) = 6.0.0
 Provides:  bundled(npm(lodash)) = 4.18.1
-Provides:  bundled(npm(lodash.merge)) = 4.6.2
 Provides:  bundled(npm(loose-envify)) = 1.4.0
 Provides:  bundled(npm(memoize-one)) = 5.2.1
-Provides:  bundled(npm(minimatch)) = 3.1.5
-Provides:  bundled(npm(ms)) = 2.1.3
-Provides:  bundled(npm(natural-compare)) = 1.4.0
 Provides:  bundled(npm(object-assign)) = 4.1.1
-Provides:  bundled(npm(once)) = 1.4.0
-Provides:  bundled(npm(optionator)) = 0.9.4
-Provides:  bundled(npm(p-limit)) = 3.1.0
-Provides:  bundled(npm(p-locate)) = 5.0.0
-Provides:  bundled(npm(parent-module)) = 1.0.1
-Provides:  bundled(npm(path-exists)) = 4.0.0
-Provides:  bundled(npm(path-is-absolute)) = 1.0.1
-Provides:  bundled(npm(path-key)) = 3.1.1
-Provides:  bundled(npm(prelude-ls)) = 1.2.1
-Provides:  bundled(npm(prettier)) = 3.3.3
+Provides:  bundled(npm(prettier)) = 3.8.3
 Provides:  bundled(npm(process-nextick-args)) = 2.0.1
 Provides:  bundled(npm(prop-types)) = 15.8.1
-Provides:  bundled(npm(punycode)) = 2.3.1
-Provides:  bundled(npm(queue-microtask)) = 1.2.3
 Provides:  bundled(npm(react)) = 18.3.1
 Provides:  bundled(npm(react-dom)) = 18.3.1
-Provides:  bundled(npm(react-dropzone)) = 14.3.5
+Provides:  bundled(npm(react-dropzone)) = 14.4.1
 Provides:  bundled(npm(react-fast-compare)) = 3.2.2
 Provides:  bundled(npm(react-is)) = 16.13.1
 Provides:  bundled(npm(readable-stream)) = 2.3.8
 Provides:  bundled(npm(remarkable)) = 2.0.1
-Provides:  bundled(npm(resolve-from)) = 4.0.0
-Provides:  bundled(npm(reusify)) = 1.0.4
-Provides:  bundled(npm(rimraf)) = 3.0.2
-Provides:  bundled(npm(run-parallel)) = 1.2.0
 Provides:  bundled(npm(safe-buffer)) = 5.2.1
 Provides:  bundled(npm(safer-buffer)) = 2.1.2
 Provides:  bundled(npm(scheduler)) = 0.23.2
-Provides:  bundled(npm(shebang-command)) = 2.0.0
-Provides:  bundled(npm(shebang-regex)) = 3.0.0
 Provides:  bundled(npm(sprintf-js)) = 1.0.3
 Provides:  bundled(npm(string_decoder)) = 1.1.1
-Provides:  bundled(npm(strip-ansi)) = 6.0.1
-Provides:  bundled(npm(strip-json-comments)) = 3.1.1
-Provides:  bundled(npm(supports-color)) = 7.2.0
-Provides:  bundled(npm(tabbable)) = 6.2.0
-Provides:  bundled(npm(text-table)) = 0.2.0
+Provides:  bundled(npm(tabbable)) = 6.4.0
 Provides:  bundled(npm(throttle-debounce)) = 5.0.2
 Provides:  bundled(npm(tslib)) = 2.8.1
-Provides:  bundled(npm(type-check)) = 0.4.0
-Provides:  bundled(npm(type-fest)) = 0.20.2
-Provides:  bundled(npm(uri-js)) = 4.4.1
 Provides:  bundled(npm(util-deprecate)) = 1.0.2
-Provides:  bundled(npm(uuid)) = 10.0.0
-Provides:  bundled(npm(victory-area)) = 37.3.1
-Provides:  bundled(npm(victory-axis)) = 37.3.1
-Provides:  bundled(npm(victory-bar)) = 37.3.1
-Provides:  bundled(npm(victory-box-plot)) = 37.3.1
-Provides:  bundled(npm(victory-brush-container)) = 37.3.1
-Provides:  bundled(npm(victory-chart)) = 37.3.1
-Provides:  bundled(npm(victory-core)) = 37.3.1
-Provides:  bundled(npm(victory-create-container)) = 37.3.1
-Provides:  bundled(npm(victory-cursor-container)) = 37.3.1
-Provides:  bundled(npm(victory-group)) = 37.3.1
-Provides:  bundled(npm(victory-legend)) = 37.3.1
-Provides:  bundled(npm(victory-line)) = 37.3.1
-Provides:  bundled(npm(victory-pie)) = 37.3.1
-Provides:  bundled(npm(victory-polar-axis)) = 37.3.1
-Provides:  bundled(npm(victory-scatter)) = 37.3.1
-Provides:  bundled(npm(victory-selection-container)) = 37.3.1
-Provides:  bundled(npm(victory-shared-events)) = 37.3.1
-Provides:  bundled(npm(victory-stack)) = 37.3.1
-Provides:  bundled(npm(victory-tooltip)) = 37.3.1
-Provides:  bundled(npm(victory-vendor)) = 37.3.1
-Provides:  bundled(npm(victory-voronoi-container)) = 37.3.1
-Provides:  bundled(npm(victory-zoom-container)) = 37.3.1
-Provides:  bundled(npm(which)) = 2.0.2
-Provides:  bundled(npm(word-wrap)) = 1.2.5
-Provides:  bundled(npm(wrappy)) = 1.0.2
-Provides:  bundled(npm(yocto-queue)) = 0.1.0
+Provides:  bundled(npm(uuid)) = 14.0.0
+Provides:  bundled(npm(victory-area)) = 37.3.6
+Provides:  bundled(npm(victory-axis)) = 37.3.6
+Provides:  bundled(npm(victory-bar)) = 37.3.6
+Provides:  bundled(npm(victory-box-plot)) = 37.3.6
+Provides:  bundled(npm(victory-brush-container)) = 37.3.6
+Provides:  bundled(npm(victory-chart)) = 37.3.6
+Provides:  bundled(npm(victory-core)) = 37.3.6
+Provides:  bundled(npm(victory-create-container)) = 37.3.6
+Provides:  bundled(npm(victory-cursor-container)) = 37.3.6
+Provides:  bundled(npm(victory-group)) = 37.3.6
+Provides:  bundled(npm(victory-legend)) = 37.3.6
+Provides:  bundled(npm(victory-line)) = 37.3.6
+Provides:  bundled(npm(victory-pie)) = 37.3.6
+Provides:  bundled(npm(victory-polar-axis)) = 37.3.6
+Provides:  bundled(npm(victory-scatter)) = 37.3.6
+Provides:  bundled(npm(victory-selection-container)) = 37.3.6
+Provides:  bundled(npm(victory-shared-events)) = 37.3.6
+Provides:  bundled(npm(victory-stack)) = 37.3.6
+Provides:  bundled(npm(victory-tooltip)) = 37.3.6
+Provides:  bundled(npm(victory-vendor)) = 37.3.6
+Provides:  bundled(npm(victory-voronoi-container)) = 37.3.6
+Provides:  bundled(npm(victory-zoom-container)) = 37.3.6
 ##### Bundled cargo crates list - END #####
 
-# Attach the buildrequires to the top level package:
+# BuildRequires for the main package:
 BuildRequires:    nspr-devel
 BuildRequires:    nss-devel >= 3.34
 BuildRequires:    openldap-clients
@@ -393,6 +303,7 @@ BuildRequires:    pcre2-devel
 BuildRequires:    cracklib-devel
 BuildRequires:    json-c-devel
 BuildRequires:    libxcrypt-devel
+BuildRequires:    zlib-devel
 %if %{with clang}
 BuildRequires:    libatomic
 BuildRequires:    clang
@@ -417,63 +328,78 @@ BuildRequires:    libdb-devel
 %endif
 %endif
 
-# The following are needed to build the snmp ldap-agent
+# For SNMP ldap-agent
 BuildRequires:    net-snmp-devel
 BuildRequires:    bzip2-devel
 BuildRequires:    openssl-devel
-# the following is for the pam passthru auth plug-in
+# For HIBP password breach checking
+%if %{with hibp}
+BuildRequires:    libcurl-devel
+%endif
+# For PAM passthru auth plug-in
 BuildRequires:    pam-devel
 BuildRequires:    systemd-units
 BuildRequires:    systemd-devel
 BuildRequires:    systemd-rpm-macros
 %{?sysusers_requires_compat}
+%if %{with usdt}
+BuildRequires:    systemtap-sdt-devel
+%endif
 BuildRequires:    cargo
 BuildRequires:    rust
 BuildRequires:    pkgconfig
 BuildRequires:    pkgconfig(systemd)
 BuildRequires:    pkgconfig(krb5)
 BuildRequires:    pkgconfig(libpcre2-8)
-# Needed to support regeneration of the autotool artifacts.
+# For autotools regeneration
 BuildRequires:    autoconf
 BuildRequires:    automake
 BuildRequires:    libtool
-# For our documentation
+BuildRequires:    make
+# For doxygen documentation
 BuildRequires:    doxygen
-# For tests!
+# For cmocka unit tests
 BuildRequires:    libcmocka-devel
-# For lib389 and related components.
+# For lib389
 BuildRequires:    python%{python3_pkgversion}-devel
 
 # For cockpit
 %if %{with cockpit}
 BuildRequires:    rsync
-BuildRequires:    npm
-BuildRequires:    nodejs
+BuildRequires:    /usr/bin/npm
+BuildRequires:    /usr/bin/node
 %endif
 
-# For autosetup -S git
-BuildRequires:    git
-
+# Runtime requires for the main package
 Requires:         %{name}-libs = %{version}-%{release}
 Requires:         python%{python3_pkgversion}-lib389 = %{version}-%{release}
 
-# this is needed for using semanage from our setup scripts
+# For semanage in setup scripts
 Requires:         policycoreutils-python-utils
 Requires:         libsemanage-python%{python3_pkgversion}
-# the following are needed for some of our scripts
+# NoNewPrivileges support in dirsrv service units requires
+# selinux-policy with init_nnp_daemon_domain for dirsrv_t.
+# See: https://bugzilla.redhat.com/show_bug.cgi?id=2457951
+%if 0%{?rhel} == 10
+Requires:         selinux-policy >= 42.1.22-1
+%endif
+%if 0%{?fedora} == 43
+Requires:         selinux-policy >= 43.7-1
+%endif
+%if 0%{?fedora} == 44
+Requires:         selinux-policy >= 44.1-1
+%endif
+# For CLI scripts
 Requires:         openldap-clients
 Requires:         acl
-# this is needed to setup SSL if you are not using the
-# administration server package
+# For SSL/TLS setup
 Requires:         nss-tools
 %dirsrv_requires_ge nss
-# these are not found by the auto-dependency method
-# they are required to support the mandatory LDAP SASL mechs
+# Mandatory LDAP SASL mechanisms
 Requires:         cyrus-sasl-gssapi
 Requires:         cyrus-sasl-md5
-# This is optionally supported by us, as we use it in our tests
 Requires:         cyrus-sasl-plain
-# this is needed for backldbm
+# For back-ldbm
 %if %{with libbdb_ro}
 Requires:         %{name}-robdb-libs = %{version}-%{release}
 %else
@@ -482,6 +408,7 @@ Requires:         libdb
 %endif
 %endif
 Requires:         lmdb-libs
+
 # Needed by logconv.pl
 %if %{without libbdb_ro}
 %if %{without bundle_libdb}
@@ -493,62 +420,44 @@ Requires:         perl-Archive-Tar
 Requires:         perl-debugger
 Requires:         perl-sigtrap
 %endif
+
 # Needed for password dictionary checks
 Requires:         cracklib-dicts
 Requires:         json-c
-# Log compression
-Requires:         zlib-devel
 # logconv.py, MIME type
 Requires:         python3-file-magic
 # Picks up our systemd deps.
 %{?systemd_requires}
+# Ensure sysusers are created
+%{?sysusers_requires_compat}
+
+%if %{with usdt}
+# Optional eBPF tracer for the shipped USDT bpftrace scripts.
+# Use Suggests so it is not installed by default (debug-only, pulls in gcc).
+Suggests:         bpftrace
+%endif
 
-Source0:          https://github.com/389ds/%{name}/releases/download/%{name}-%{version}/%{name}-%{version}.tar.bz2
+
+Source0:          %{name}-%{version}.tar.bz2
 Source2:          %{name}-devel.README
 %if %{with bundle_jemalloc}
 Source3:          https://github.com/jemalloc/%{jemalloc_name}/releases/download/%{jemalloc_ver}/%{jemalloc_name}-%{jemalloc_ver}.tar.bz2
+Source5:          jemalloc-5.3.0_throw_bad_alloc.patch
 %endif
 Source4:          389-ds-base.sysusers
 %if %{with bundle_libdb}
-Source5:          https://fedorapeople.org/groups/389ds/libdb-5.3.28-59.tar.bz2
-%endif
-
-Source6:          vendor-%{version}-1.tar.gz
-Source7:          Cargo-%{version}-1.lock
-Source8:          cockpit_dist-%{version}-1.tar.bz2
-
-Patch:            0001-Issue-7150-Compressed-access-log-rotations-skipped-a.patch
-Patch:            0002-Sync-lib389-version-to-3.1.4-7161.patch
-Patch:            0003-Issue-7166-db_config_set-asserts-because-of-dynamic-.patch
-Patch:            0004-Issue-7160-Add-lib389-version-sync-check-to-configur.patch
-Patch:            0005-Issue-7096-During-replication-online-total-init-the-.patch
-Patch:            0006-Issue-Revise-paged-result-search-locking.patch
-Patch:            0007-Issue-7108-Fix-shutdown-crash-in-entry-cache-destruc.patch
-Patch:            0008-Issue-7172-Index-ordering-mismatch-after-upgrade-717.patch
-Patch:            0009-Issue-7172-2nd-Index-ordering-mismatch-after-upgrade.patch
-Patch:            0010-Bump-lodash-from-4.17.21-to-4.17.23-in-src-cockpit-3.patch
-Patch:            0011-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch
-Patch:            0012-Issue-7198-Web-console-doesn-t-show-sub-suffix-when-.patch
-Patch:            0013-Issue-7184-argparse.HelpFormatter-_format_actions_us.patch
-Patch:            0014-Issue-7027-2nd-389-ds-base-OpenScanHub-Leaks-Detecte.patch
-Patch:            0015-Issue-7213-MDB_BAD_VALSIZE-error-while-handling-VLV-.patch
-Patch:            0016-Issue-7194-Repl-Log-Analysis-Add-CSN-propagation-det.patch
-Patch:            0017-Issue-7224-CI-Test-Simplify-test_reserve_descriptor_.patch
-Patch:            0018-Issue-7223-Revert-index-scan-limits-for-system-index.patch
-Patch:            0019-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch
-Patch:            0020-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch
-Patch:            0021-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch
-Patch:            0022-Issue-7223-Add-dsctl-index-check-command-for-offline.patch
-Patch:            0023-Issue-7184-2nd-argparse.HelpFormatter-_format_action.patch
-Patch:            0024-Issue-7223-Use-lexicographical-order-for-ancestorid-.patch
-Patch:            0025-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch
-Patch:            0026-Security-fix-for-CVE-2025-14905.patch
-Patch:            0027-Issue-7302-dblib-bdb2mdb-fails-on-F43-F43-upgrade-73.patch
-Patch:            0028-Issue-7267-MDB_BAD_VALSIZE-error-when-updating-index.patch
-Patch:            0029-Issue-7503-CVE-2026-9064-Add-a-limit-to-the-number-c.patch
+Source6:          https://fedorapeople.org/groups/389ds/libdb-5.3.28-59.tar.bz2
+%endif
+
+# To override vendor/Cargo.lock/cockpit_dist from the upstream tarball
+# with separately uploaded sources
+# uncomment and replace double percent with a single one
+#Source6:          vendor-%%{version}-1.tar.gz
+#Source7:          Cargo-%%{version}-1.lock
+#Source8:          cockpit_dist-%%{version}-1.tar.bz2
 
 %description
-389 Directory Server is an LDAPv3 compliant server.  The base package includes
+389 Directory Server is an LDAPv3 compliant server. The base package includes
 the LDAP server and command line utilities for server administration.
 %if %{with asan}
 WARNING! This build is linked to Address Sanitisation libraries. This probably
@@ -559,7 +468,7 @@ Please see http://seclists.org/oss-sec/2016/q1/363 for more information.
 %if %{with libbdb_ro}
 %package        robdb-libs
 Summary:        Read-only Berkeley Database Library
-License:        GPL-3.0-or-later WITH GPL-3.0-389-ds-base-exception AND (Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND (Apache-2.0 OR LGPL-2.1-or-later OR MIT) AND (Apache-2.0 OR MIT) AND (CC-BY-4.0 AND MIT) AND (MIT OR Apache-2.0) AND Unicode-3.0 AND (MIT OR CC0-1.0) AND (MIT OR Unlicense) AND 0BSD AND Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND ISC AND MIT AND MIT AND ISC AND MPL-2.0 AND PSF-2.0 AND Zlib
+License:          GPL-3.0-or-later WITH GPL-3.0-389-ds-base-exception AND (Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND (Apache-2.0 OR LGPL-2.1-or-later OR MIT) AND (Apache-2.0 OR MIT) AND (Apache-2.0 OR MIT) AND Unicode-3.0 AND (CC-BY-4.0 AND MIT) AND (MIT OR Unlicense) AND 0BSD AND Apache-2.0 AND BSD-3-Clause AND ISC AND MIT AND MIT AND ISC AND MPL-2.0 AND Zlib
 
 %description    robdb-libs
 The %{name}-robdb-lib package contains a library derived from rpm
@@ -570,16 +479,13 @@ some basic functions to search and read Berkeley Database records
 
 %package          libs
 Summary:          Core libraries for 389 Directory Server (%{variant})
-Provides:         svrcore = 4.1.4
-Obsoletes:        svrcore <= 4.1.3
-Conflicts:        svrcore
 %dirsrv_requires_ge nss
 Requires:         nspr
 Requires:         openldap
 Requires:         systemd-libs
-# Pull in sasl
+# SASL
 Requires:         cyrus-sasl-lib
-# KRB
+# Kerberos
 Requires:         krb5-libs
 %if %{with clang}
 Requires:         llvm
@@ -603,60 +509,71 @@ package to be installed with just the -libs package and without the main package
 
 %package          devel
 Summary:          Development libraries for 389 Directory Server (%{variant})
-Provides:         svrcore-devel = 4.1.4
-Obsoletes:        svrcore-devel <= 4.1.3
-Conflicts:        svrcore-devel
 Requires:         %{name}-libs = %{version}-%{release}
 Requires:         pkgconfig
 Requires:         nspr-devel
 Requires:         nss-devel >= 3.34
 Requires:         openldap-devel
-# systemd-libs contains the headers iirc.
+# For systemd headers
 Requires:         systemd-libs
 
 %description      devel
 Development Libraries and headers for the 389 Directory Server base package.
 
+
 %package          snmp
 Summary:          SNMP Agent for 389 Directory Server
 Requires:         %{name} = %{version}-%{release}
 
-Obsoletes:        %{name} <= 1.4.0.0
-
 %description      snmp
 SNMP Agent for the 389 Directory Server base package.
 
 %if %{with bundle_libdb}
 %package          bdb
 Summary:          Berkeley Database backend for 389 Directory Server
-%description      bdb
-Berkeley Database backend for 389 Directory Server
-Warning! This backend is deprecated in favor of lmdb and its support
-may be removed in future versions.
 
 Requires:         %{name} = %{version}-%{release}
+Requires:         %{name}-libs = %{version}-%{release}
 # Berkeley DB database libdb was marked as deprecated since F40:
 # https://fedoraproject.org/wiki/Changes/389_Directory_Server_3.0.0
 # because libdb was marked as deprecated since F33
 # https://fedoraproject.org/wiki/Changes/Libdb_deprecated
 Provides:         deprecated()
-%endif
 
+%description      bdb
+Berkeley Database backend for 389 Directory Server
+Warning! This backend is deprecated in favor of lmdb and its support
+may be removed in future versions.
+%endif
 
 %package -n python%{python3_pkgversion}-lib389
 Summary:  A library for accessing, testing, and configuring the 389 Directory Server
 BuildArch:        noarch
 Requires: %{name} = %{version}-%{release}
 Requires: openssl
-# This is for /usr/bin/c_rehash tool, only needed for openssl < 1.1.0
-Requires: openssl-perl
 Requires: iproute
 Requires: python%{python3_pkgversion}-libselinux
 Recommends: bash-completion
 
 %description -n python%{python3_pkgversion}-lib389
 This module contains tools and libraries for accessing, testing,
- and configuring the 389 Directory Server.
+and configuring the 389 Directory Server.
+
+%if %{with repl_reports}
+%package -n python%{python3_pkgversion}-lib389-repl-reports
+Summary: HTML and PNG report generation for 389 Directory Server replication monitoring tools
+BuildArch: noarch
+Requires: python%{python3_pkgversion}-lib389 = %{version}-%{release}
+Requires: python%{python3_pkgversion}-plotly
+Requires: python%{python3_pkgversion}-matplotlib
+
+%description -n python%{python3_pkgversion}-lib389-repl-reports
+Extended reporting capabilities for 389 Directory Server replication analysis.
+This package provides additional report formats (HTML and PNG) with interactive
+visualizations and graphs for replication lag analysis. These formats require
+additional dependencies and are optional - the base package supports CSV
+reports without this extension.
+%endif
 
 %if %{with cockpit}
 %package -n cockpit-389-ds
@@ -678,7 +595,7 @@ cd src/lib389
 %pyproject_buildrequires -g test
 
 %prep
-%autosetup -S git -p1 -n %{name}-%{version}
+%autosetup -p1 -n %{name}-%{version}
 %if %{defined SOURCE6}
 rm -rf vendor
 tar xzf %{SOURCE6}
@@ -692,7 +609,7 @@ cp %{SOURCE7} src/Cargo.lock
 %endif
 
 %if %{with bundle_libdb}
-%setup -q -n %{name}-%{version} -T -D -b 5
+%setup -q -n %{name}-%{version} -T -D -b 6
 %endif
 
 %if %{defined SOURCE8}
@@ -703,15 +620,10 @@ tar xvjf %{SOURCE8} -C src/cockpit/389-console
 cp %{SOURCE2} README.devel
 
 %build
-# Workaround until https://github.com/389ds/389-ds-base/issues/6476 is fixed
-export CFLAGS="%{optflags} -std=gnu17"
-
 %if %{with clang}
 CLANG_FLAGS="--enable-clang"
 %endif
 
-%{?with_tmpfiles_d: TMPFILES_FLAG="--with-tmpfiles-d=%{with_tmpfiles_d}"}
-
 %if %{with asan}
 ASAN_FLAGS="--enable-asan --enable-debug"
 %endif
@@ -734,6 +646,12 @@ RUST_FLAGS="--enable-rust --enable-rust-offline"
 COCKPIT_FLAGS="--disable-cockpit"
 %endif
 
+%if %{with usdt}
+USDT_FLAGS="--enable-usdt"
+%else
+USDT_FLAGS="--disable-usdt"
+%endif
+
 %if %{with bundle_jemalloc}
 # Override page size, bz #1545539
 # 4K
@@ -754,6 +672,7 @@ COCKPIT_FLAGS="--disable-cockpit"
 
 # Build jemalloc
 pushd ../%{jemalloc_name}-%{jemalloc_ver}
+patch -p1 -F3 < %{SOURCE5}
 %configure \
         --libdir=%{_libdir}/%{pkgname}/lib \
         --bindir=%{_libdir}/%{pkgname}/bin \
@@ -766,14 +685,16 @@ popd
 %if %{with bundle_libdb}
 mkdir -p ../%{libdb_base_version}
 pushd ../%{libdb_base_version}
-tar -xjf  %{_topdir}/SOURCES/%{libdb_full_version}.tar.bz2
+tar -xjf %{_topdir}/SOURCES/%{libdb_full_version}.tar.bz2
 mv %{libdb_full_version} SOURCES
-sed -i -e '/^CFLAGS=/s/-fno-strict-aliasing/& -std=gnu99/' %{_builddir}/%{name}-%{version}/rpm/bundle-libdb.spec
-rpmbuild  --define "_topdir $PWD" -bc %{_builddir}/%{name}-%{version}/rpm/bundle-libdb.spec
+%if 0%{?fedora}
+sed -i -e '/^CFLAGS=/s/-fno-strict-aliasing/& -std=gnu99/' %{_builddir}/%{name}-%{version}/rpm/bundle-libdb.spec.in
+%endif
+rpmbuild  --define "_topdir $PWD" -bc %{_builddir}/%{name}-%{version}/rpm/bundle-libdb.spec.in
 popd
 %endif
 
-# Rebuild the autotool artifacts now.
+# Rebuild autotools artifacts
 autoreconf -fiv
 
 %configure \
@@ -785,23 +706,19 @@ autoreconf -fiv
 %if %{with bundle_libdb}
            --with-bundle-libdb=%{_builddir}/%{libdb_base_version}/BUILD/%{libdb_base_dir}/dist/dist-tls \
 %endif
-           --with-selinux $TMPFILES_FLAG \
+           --with-selinux \
            --with-systemd \
            --with-systemdsystemunitdir=%{_unitdir} \
            --with-systemdsystemconfdir=%{_sysconfdir}/systemd/system \
            --with-systemdgroupname=%{groupname} \
            --libexecdir=%{_libexecdir}/%{pkgname} \
-           $ASAN_FLAGS $MSAN_FLAGS $TSAN_FLAGS $UBSAN_FLAGS $RUST_FLAGS $CLANG_FLAGS $COCKPIT_FLAGS \
-%if 0%{?fedora} >= 34 || 0%{?rhel} >= 9
+           $ASAN_FLAGS $MSAN_FLAGS $TSAN_FLAGS $UBSAN_FLAGS $RUST_FLAGS $CLANG_FLAGS $COCKPIT_FLAGS $USDT_FLAGS \
            --with-libldap-r=no \
+%if %{with hibp}
+           --enable-hibp \
 %endif
            --enable-cmocka
 
-# Avoid "Unknown key name 'XXX' in section 'Service', ignoring." warnings from systemd on older releases
-%if 0%{?rhel} && 0%{?rhel} < 9
-  sed -r -i '/^(Protect(Home|Hostname|KernelLogs)|PrivateMounts)=/d' %{_builddir}/%{name}-%{version}/wrappers/*.service.in
-%endif
-
 # lib389
 pushd ./src/lib389
 %{python3} validate_version.py --update
@@ -809,7 +726,7 @@ pushd ./src/lib389
 popd
 
 # Generate symbolic info for debuggers
-export XCFLAGS=$RPM_OPT_FLAGS
+export XCFLAGS="%{optflags}"
 
 %make_build
 
@@ -832,11 +749,16 @@ sed -i -e "/libback-bdb/d" plugins.list
 %endif
 
 # Copy in our docs from doxygen.
-cp -r %{_builddir}/%{name}-%{version}/man/man3 $RPM_BUILD_ROOT/%{_mandir}/man3
+cp -r %{_builddir}/%{name}-%{version}/man/man3 %{buildroot}/%{_mandir}/man3
 
 # lib389
 pushd src/lib389
 %pyproject_install
+%if 0%{?fedora} <= 41 || (0%{?rhel} && 0%{?rhel} <= 10)
+for clitool in dsconf dscreate dsctl dsidm openldap_to_ds; do
+    mv %{buildroot}%{_bindir}/$clitool %{buildroot}%{_sbindir}/
+done
+%endif
 %pyproject_save_files -l lib389
 popd
 
@@ -847,26 +769,26 @@ do
     install -p -m 0644 -D -t '%{buildroot}%{bash_completions_dir}' "${clitool}"
 done
 
-mkdir -p $RPM_BUILD_ROOT/var/log/%{pkgname}
-mkdir -p $RPM_BUILD_ROOT/var/lib/%{pkgname}
-mkdir -p $RPM_BUILD_ROOT/var/lock/%{pkgname} \
-    && chmod 770 $RPM_BUILD_ROOT/var/lock/%{pkgname}
+mkdir -p %{buildroot}/var/log/%{pkgname}
+mkdir -p %{buildroot}/var/lib/%{pkgname}
+mkdir -p %{buildroot}/var/lock/%{pkgname} \
+    && chmod 770 %{buildroot}/var/lock/%{pkgname}
 
-# for systemd
-mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/systemd/system/%{groupname}.wants
+# For systemd
+mkdir -p %{buildroot}%{_sysconfdir}/systemd/system/%{groupname}.wants
 install -p -D -m 0644 %{SOURCE4} %{buildroot}%{_sysusersdir}/389-ds-base.conf
 
-#remove libtool and static libs
-rm -f $RPM_BUILD_ROOT%{_libdir}/%{pkgname}/*.a
-rm -f $RPM_BUILD_ROOT%{_libdir}/%{pkgname}/*.la
-rm -f $RPM_BUILD_ROOT%{_libdir}/%{pkgname}/plugins/*.a
-rm -f $RPM_BUILD_ROOT%{_libdir}/%{pkgname}/plugins/*.la
-rm -f $RPM_BUILD_ROOT%{_libdir}/libsvrcore.a
-rm -f $RPM_BUILD_ROOT%{_libdir}/libsvrcore.la
+# Remove libtool and static libs
+rm -f %{buildroot}%{_libdir}/%{pkgname}/*.a
+rm -f %{buildroot}%{_libdir}/%{pkgname}/*.la
+rm -f %{buildroot}%{_libdir}/%{pkgname}/plugins/*.a
+rm -f %{buildroot}%{_libdir}/%{pkgname}/plugins/*.la
+rm -f %{buildroot}%{_libdir}/libsvrcore.a
+rm -f %{buildroot}%{_libdir}/libsvrcore.la
 
 %if %{with bundle_jemalloc}
 pushd ../%{jemalloc_name}-%{jemalloc_ver}
-make DESTDIR="$RPM_BUILD_ROOT" install_lib install_bin
+make DESTDIR="%{buildroot}" install_lib install_bin
 cp -pa COPYING ../%{name}-%{version}/COPYING.jemalloc
 cp -pa README ../%{name}-%{version}/README.jemalloc
 popd
@@ -879,7 +801,7 @@ libdbdestdir=$PWD/../%{name}-%{version}
 cp -pa $libdbbuilddir/LICENSE $libdbdestdir/LICENSE.libdb
 cp -pa $libdbbuilddir/README $libdbdestdir/README.libdb
 cp -pa $libdbbuilddir/lgpl-2.1.txt $libdbdestdir/lgpl-2.1.txt.libdb
-cp -pa $libdbbuilddir/dist/dist-tls/.libs/%{libdb_bundle_name} $RPM_BUILD_ROOT%{_libdir}/%{pkgname}/%{libdb_bundle_name}
+cp -pa $libdbbuilddir/dist/dist-tls/.libs/%{libdb_bundle_name} %{buildroot}%{_libdir}/%{pkgname}/%{libdb_bundle_name}
 popd
 %endif
 
@@ -900,12 +822,12 @@ popd
 %endif
 
 %check
-# This checks the code, if it fails it prints why, then re-raises the fail to shortcircuit the rpm build.
+# Run cmocka unit tests
 %if %{with tsan}
 export TSAN_OPTIONS=print_stacktrace=1:second_deadlock_stack=1:history_size=7
 %endif
 %if %{without asan} && %{without msan}
-if ! make DESTDIR="$RPM_BUILD_ROOT" check; then cat ./test-suite.log && false; fi
+if ! make DESTDIR="%{buildroot}" check; then cat ./test-suite.log && false; fi
 %endif
 
 # Check import for lib389 modules
@@ -914,19 +836,17 @@ if ! make DESTDIR="$RPM_BUILD_ROOT" check; then cat ./test-suite.log && false; f
 %post
 if [ -n "$DEBUGPOSTTRANS" ] ; then
     output=$DEBUGPOSTTRANS
-    output2=${DEBUGPOSTTRANS}.upgrade
 else
     output=/dev/null
-    output2=/dev/null
 fi
 
-# reload to pick up any changes to systemd files
+# Reload to pick up any changes to systemd files
 /bin/systemctl daemon-reload >$output 2>&1 || :
 
-# Reload our sysctl before we restart (if we can)
-sysctl --system &> $output; true
+# Reload sysctl before restarting instances
+sysctl --system &> "$output"; true
 
-# Gather running instances, stop them, then restart
+# Gather running instances, stop and restart them
 instbase="%{_sysconfdir}/%{pkgname}"
 instances=""
 ninst=0
@@ -937,7 +857,6 @@ for dir in "$instbase"/slapd-* ; do
     case "$dir" in *.removed) continue ;; esac
     basename=$(basename "$dir")
     inst="%{pkgname}@${basename#slapd-}"
-    inst_name="${basename#slapd-}"
     echo "found instance $inst - getting status" >> "$output" 2>&1 || :
     if /bin/systemctl -q is-active "$inst" ; then
        echo "instance $inst is running - stopping for upgrade" >> "$output" 2>&1 || :
@@ -980,7 +899,6 @@ fi
 %postun snmp
 %systemd_postun_with_restart %{pkgname}-snmp.service
 
-exit 0
 
 %files -f plugins.list
 %if %{with bundle_jemalloc}
@@ -994,7 +912,7 @@ exit 0
 %config(noreplace)%{_sysconfdir}/%{pkgname}/schema/*.ldif
 %dir %{_sysconfdir}/%{pkgname}/config
 %dir %{_sysconfdir}/systemd/system/%{groupname}.wants
-%{_sysusersdir}/389-ds-base.conf
+%{_sysusersdir}/%{name}.conf
 %config(noreplace)%{_sysconfdir}/%{pkgname}/config/slapd-collations.conf
 %config(noreplace)%{_sysconfdir}/%{pkgname}/config/certmap.conf
 %{_datadir}/%{pkgname}
@@ -1028,8 +946,6 @@ exit 0
 %{_mandir}/man5/dirsrv.systemd.5.gz
 %{_libdir}/%{pkgname}/python
 %dir %{_libdir}/%{pkgname}/plugins
-# This has to be hardcoded to /lib - $libdir changes between lib/lib64, but
-# sysctl.d is always in /lib.
 %{_prefix}/lib/sysctl.d/*
 %dir %{_localstatedir}/lib/%{pkgname}
 %dir %{_localstatedir}/log/%{pkgname}
@@ -1093,11 +1009,19 @@ exit 0
 %doc src/lib389/README.md
 %license LICENSE LICENSE.GPLv3+
 # Binaries
+%if 0%{?fedora} >= 42 || 0%{?rhel} >= 11
 %{_bindir}/dsconf
 %{_bindir}/dscreate
 %{_bindir}/dsctl
 %{_bindir}/dsidm
 %{_bindir}/openldap_to_ds
+%else
+%{_sbindir}/dsconf
+%{_sbindir}/dscreate
+%{_sbindir}/dsctl
+%{_sbindir}/dsidm
+%{_sbindir}/openldap_to_ds
+%endif
 %{_libexecdir}/%{pkgname}/dscontainer
 # Man pages
 %{_mandir}/man8/dsconf.8.gz
@@ -1112,6 +1036,11 @@ exit 0
 %{bash_completions_dir}/dscreate
 %{bash_completions_dir}/dsidm
 
+%if %{with repl_reports}
+%files -n python%{python3_pkgversion}-lib389-repl-reports
+# No files needed as this is just a meta-package for dependencies
+%endif
+
 %if %{with cockpit}
 %files -n cockpit-389-ds -f cockpit.list
 %{_datarootdir}/metainfo/389-console/org.port389.cockpit_console.metainfo.xml

diff --git a/389-ds-base.sysusers b/389-ds-base.sysusers
index 32a3452..4411113 100644
--- a/389-ds-base.sysusers
+++ b/389-ds-base.sysusers
@@ -1,3 +1,3 @@
 #Type Name     ID             GECOS                   Home directory       Shell
 g     dirsrv   389
-u     dirsrv   389:389        "user for 389-ds-base"  /usr/share/dirsrv/   /sbin/nologin
+u     dirsrv   389:dirsrv     "user for 389-ds-base"  /usr/share/dirsrv/   /sbin/nologin

diff --git a/jemalloc-5.3.0_throw_bad_alloc.patch b/jemalloc-5.3.0_throw_bad_alloc.patch
new file mode 100644
index 0000000..94e4d36
--- /dev/null
+++ b/jemalloc-5.3.0_throw_bad_alloc.patch
@@ -0,0 +1,41 @@
+#commit 3de0c24859f4413bf03448249078169bb50bda0f
+#Author: divanorama <divanorama@gmail.com>
+#Date:   Thu Sep 29 23:35:59 2022 +0200
+#
+#    Disable builtin malloc in tests
+#    
+#    With `--with-jemalloc-prefix=` and without `-fno-builtin` or `-O1` both clang and gcc may optimize out `malloc` calls
+#    whose result is unused. Comparing result to NULL also doesn't necessarily count as being used.
+#    
+#    This won't be a problem in most client programs as this only concerns really unused pointers, but in
+#    tests it's important to actually execute allocations.
+#    `-fno-builtin` should disable this optimization for both gcc and clang, and applying it only to tests code shouldn't hopefully be an issue.
+#    Another alternative is to force "use" of result but that'd require more changes and may miss some other optimization-related issues.
+#    
+#    This should resolve https://github.com/jemalloc/jemalloc/issues/2091
+#
+#diff --git a/Makefile.in b/Makefile.in
+#index 6809fb29..a964f07e 100644
+#--- a/Makefile.in
+#+++ b/Makefile.in
+#@@ -458,6 +458,8 @@ $(TESTS_OBJS): $(objroot)test/%.$(O): $(srcroot)test/%.c
+# $(TESTS_CPP_OBJS): $(objroot)test/%.$(O): $(srcroot)test/%.cpp
+# $(TESTS_OBJS): CPPFLAGS += -I$(srcroot)test/include -I$(objroot)test/include
+# $(TESTS_CPP_OBJS): CPPFLAGS += -I$(srcroot)test/include -I$(objroot)test/include
+#+$(TESTS_OBJS): CFLAGS += -fno-builtin
+#+$(TESTS_CPP_OBJS): CPPFLAGS += -fno-builtin
+# ifneq ($(IMPORTLIB),$(SO))
+# $(CPP_OBJS) $(C_SYM_OBJS) $(C_OBJS) $(C_JET_SYM_OBJS) $(C_JET_OBJS): CPPFLAGS += -DDLLEXPORT
+# endif
+diff --git a/src/jemalloc_cpp.cpp b/src/jemalloc_cpp.cpp
+index fffd6aee..5a682991 100644
+--- a/src/jemalloc_cpp.cpp
++++ b/src/jemalloc_cpp.cpp
+@@ -93,7 +93,7 @@ handleOOM(std::size_t size, bool nothrow) {
+        }
+
+        if (ptr == nullptr && !nothrow)
+-		std::__throw_bad_alloc();
++		throw std::bad_alloc();
+        return ptr;
+ }

diff --git a/main.fmf b/main.fmf
index 76d16bf..bd58518 100644
--- a/main.fmf
+++ b/main.fmf
@@ -10,7 +10,7 @@
         package: [389-ds-base, git, pytest]
       - name: clone repo
         how: shell
-        script: git clone https://github.com/389ds/389-ds-base /root/ds
+        script: git clone -b 389-ds-base-3.1 https://github.com/389ds/389-ds-base /root/ds
 /test:
     /upstream_basic:
         test: pytest -v /root/ds/dirsrvtests/tests/suites/basic/basic_test.py

diff --git a/sources b/sources
index b6b2faf..0058c74 100644
--- a/sources
+++ b/sources
@@ -1,6 +1,3 @@
 SHA512 (jemalloc-5.3.0.tar.bz2) = 22907bb052096e2caffb6e4e23548aecc5cc9283dce476896a2b1127eee64170e3562fa2e7db9571298814a7a2c7df6e8d1fbe152bd3f3b0c1abec22a2de34b1
 SHA512 (libdb-5.3.28-59.tar.bz2) = 731a434fa2e6487ebb05c458b0437456eb9f7991284beb08cb3e21931e23bdeddddbc95bfabe3a2f9f029fe69cd33a2d4f0f5ce6a9811e9c3b940cb6fde4bf79
-SHA512 (389-ds-base-3.1.4.tar.bz2) = 17de77a02c848dbb8d364e7bab529726b4c32e466f47d5c2a5bba8d8b55e2a56e2b743a2efa4f820c935b39f770a621146a42443e4f171f8b14c68968155ee2c
-SHA512 (vendor-3.1.4-1.tar.gz) = 52fcb3268b863a4b8d3fe61109ab8ef231d7fc412768d8019244bc245cfface20c4531195d08d29577eaca29e87046e74083b8d33f2fb4cc0b16a6d734a854af
-SHA512 (Cargo-3.1.4-1.lock) = 949013fe4cfe30969b8411b465901514c3ea5144d329a4ea1d80244e4bd3d000db857f9b78d57a4f4f3fd240ec5b7f91ed8c39ff53a718c911ee205410bb7cc7
-SHA512 (cockpit_dist-3.1.4-1.tar.bz2) = faeaa67801f9d61f74decbc989fc05c2f6f1242879d0202482d13d5071562e529827aec78a4d9f2e19684026c5f0a2d47a4e83120b035596da2951792059ba18
+SHA512 (389-ds-base-3.1.5.tar.bz2) = 00b3abeab03b256907999082f62261831908717e380131ec4782cc8e35f9322baac72a6d760b9877b88c570f16bc6586cb302b35aa0c9c38a8b7b076febcbaf5

                 reply	other threads:[~2026-09-07 17:29 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178880219437.1.7508900179045386620.rpms-389-ds-base-666059dd015b@fedoraproject.org \
    --to=vashirov@redhat.com \
    --cc=git-commits@fedoraproject.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox