Declare root priority for multi-root optimization#

Goal: when a policy has more than one root and they disagree on a shared dependency’s version, control which root wins the newest-version tie-break instead of leaving it to incidental list order.

The conflict#

Two application roots, one shared dependency, mutually exclusive requirements on it:

# GROMACS-2025.0-foss-2025b.eb (newest GROMACS)
dependencies = [
    ('OpenMPI', '==4.1.6'),
]

# LAMMPS-2024.08-foss-2025b.eb (newest LAMMPS)
dependencies = [
    ('OpenMPI', '>=5.0.3'),
]

Both roots cannot get their newest version at once: only one OpenMPI version is co-selected per lock. Feasibility (resolvo’s CDCL SAT core) will happily return a consistent assignment, but which one depends on solver internals unless the policy says which root matters more. See the optimization model for why plain SAT feasibility is not enough here.

Without rootpriority: list order decides#

{
  "toolchain": {"name": "foss", "version": "2025b"},
  "roots": ["GROMACS", "LAMMPS"],
  "pins": [],
  "forbid": [],
  "objective": "prefer_newer"
}

With no root_priority field, priority defaults to roots list order: listing GROMACS first here keeps GROMACS 2025.0 (and OpenMPI 4.1.6), yielding LAMMPS 2023.08. Reordering roots to ["LAMMPS", "GROMACS"] flips the outcome to LAMMPS 2024.08 / GROMACS 2024.4. That coupling – priority tied to incidental list order – is exactly what root_priority lets you avoid.

With rootpriority: explicit and list-order-independent#

{
  "toolchain": {"name": "foss", "version": "2025b"},
  "roots": ["LAMMPS", "GROMACS"],
  "root_priority": ["GROMACS", "LAMMPS"],
  "pins": [],
  "forbid": [],
  "objective": "prefer_newer"
}
eb-stack stack solve \
  --easyconfigs fixtures/two_root_openmpi_conflict/easyconfigs \
  --policy fixtures/two_root_openmpi_conflict/policies/priority_gromacs_roots_lammps_first.json \
  --lock-out stack.lock.json --sbom-out stack.cdx.json
# GROMACS 2025.0 (kept: prioritized)
# LAMMPS 2023.08 (yielded: not prioritized)
# OpenMPI 4.1.6 (forced by GROMACS's exact pin)

roots still lists LAMMPS first, but root_priority: ["GROMACS", "LAMMPS"] keeps GROMACS at its newest version regardless. Swap the roots order in the policy file and re-run: the outcome does not change, because priority is now declared, not inferred.

How the priority is applied#

For each root in root_priority order (or roots order when root_priority is omitted), eb-stack pins that root to the newest version that is still jointly feasible with every higher-priority root already pinned, then moves to the next root. Lower-priority roots yield whatever version remains consistent once the higher-priority pins are fixed. A root missing from an explicit root_priority list is appended in roots order, so every root always participates.

Next steps#