It is quite common for programs to have difficult to diagnose dependency conflicts. A good example is including datomic (latest) and clojurescript (latest) in the same project. This introduces a conflict in the guava dependency (18 vs 21+) and both the errors and resolution are determined by ordering of clojurescript vs. datomic and the relative depth of their transitive dependencies in the overall dependency graph.
Being able to quickly diagnose and solve such conflicts is very important to keep existing projects working and still have freedom to upgrade libraries and dependencies as desired or necessary. I propose maybe 2 things.
First, instead of building the graph at the same time as decided on the resolution build the graph with enough information to decide on a resolution as a secondary process. This always will ensure that dependency analysis can happen at later time or different resolution algorithms can be applied.
Second, formalize the resolution algorithm. Currently I believe it is something like:
1. Root versions are 'pinned'
2. Versions lower in the graph win over versions higher in the graph (assuming leaves are higher than roots).
3. In case of tie in depth, highest version wins.
Breaking the problem down into 2 parts, the first being building a datastructure that defines all possibilities and second by filtering that graph via the resolution algorithm allows tooling a lot more visibility into potential dependency issues.