Introduce max_compatibility_level for bazel_dep (#18178)

* Add InterimModule to represent a Module before resolution finishes

The class Module currently holds data that's no longer needed after resolution finishes (such as the compatibility level, bazel compatibility, the registry where it comes from, etc). Conversely, the repo spec field is not computed until the end of resolution.

To remove runtime checks and reduce cognitive overhead, this CL splits the Module class into two; one only used after resolution finishes (Module), and one only used before (InterimModule). This allows us to introduce max_compatibility_level in a follow CL.

Work towards https://github.com/bazelbuild/bazel/issues/17378

Co-authored-by: Brentley Jones <github@brentleyjones.com>
PiperOrigin-RevId: 525780111
Change-Id: I2df8d78d324b3c8744ba0a4eda405d162e9bbb8c

* Selection with max_compatibility_level

See code comments for more details. tl;dr: we use the new `bazel_dep(max_compatibility_level=)` attribute to influence version selection.

Fixes https://github.com/bazelbuild/bazel/issues/17378

RELNOTES: Added a new `max_compatibility_level` attribute to the `bazel_dep` directive, which allows version selection to upgrade a dependency up to the specified compatibility level.

Co-authored-by: Brentley Jones <github@brentleyjones.com>
PiperOrigin-RevId: 526118928
Change-Id: I332eb3761e0dee0cb7f318cb5d8d1780fca91be8

---------

Co-authored-by: Brentley Jones <github@brentleyjones.com>
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
index bd02cc5..5c4f4ad 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
@@ -94,8 +94,10 @@
         "BazelModuleResolutionValue.java",
         "BzlmodFlagsAndEnvVars.java",
         "GitOverride.java",
+        "InterimModule.java",
         "LocalPathOverride.java",
         "Module.java",
+        "ModuleBase.java",
         "ModuleExtensionEvalStarlarkThreadContext.java",
         "ModuleFileValue.java",
         "ModuleOverride.java",
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleInspectorFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleInspectorFunction.java
index 0394de6..c2c3ffe 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleInspectorFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleInspectorFunction.java
@@ -55,7 +55,7 @@
       return null;
     }
     ImmutableMap<String, ModuleOverride> overrides = root.getOverrides();
-    ImmutableMap<ModuleKey, Module> unprunedDepGraph = resolutionValue.getUnprunedDepGraph();
+    ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph = resolutionValue.getUnprunedDepGraph();
     ImmutableMap<ModuleKey, Module> resolvedDepGraph = resolutionValue.getResolvedDepGraph();
 
     ImmutableMap<ModuleKey, AugmentedModule> depGraph =
@@ -74,7 +74,7 @@
   }
 
   public static ImmutableMap<ModuleKey, AugmentedModule> computeAugmentedGraph(
-      ImmutableMap<ModuleKey, Module> unprunedDepGraph,
+      ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph,
       ImmutableSet<ModuleKey> usedModules,
       ImmutableMap<String, ModuleOverride> overrides) {
     Map<ModuleKey, AugmentedModule.Builder> depGraphAugmentBuilder = new HashMap<>();
@@ -83,9 +83,9 @@
     // to their children AugmentedModule as dependant. Also fill in their own AugmentedModule
     // with a map from their dependencies to the resolution reason that was applied to each.
     // The newly created graph will also contain ModuleAugments for non-loaded modules.
-    for (Entry<ModuleKey, Module> e : unprunedDepGraph.entrySet()) {
+    for (Entry<ModuleKey, InterimModule> e : unprunedDepGraph.entrySet()) {
       ModuleKey parentKey = e.getKey();
-      Module parentModule = e.getValue();
+      InterimModule parentModule = e.getValue();
 
       AugmentedModule.Builder parentBuilder =
           depGraphAugmentBuilder
@@ -95,10 +95,10 @@
               .setLoaded(true);
 
       for (String childDep : parentModule.getDeps().keySet()) {
-        ModuleKey originalKey = parentModule.getOriginalDeps().get(childDep);
-        Module originalModule = unprunedDepGraph.get(originalKey);
-        ModuleKey key = parentModule.getDeps().get(childDep);
-        Module module = unprunedDepGraph.get(key);
+        ModuleKey originalKey = parentModule.getOriginalDeps().get(childDep).toModuleKey();
+        InterimModule originalModule = unprunedDepGraph.get(originalKey);
+        ModuleKey key = parentModule.getDeps().get(childDep).toModuleKey();
+        InterimModule module = unprunedDepGraph.get(key);
 
         AugmentedModule.Builder originalChildBuilder =
             depGraphAugmentBuilder.computeIfAbsent(originalKey, AugmentedModule::builder);
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunction.java
index 3c9f1c8..39092c9 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunction.java
@@ -21,8 +21,10 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
 import com.google.devtools.build.lib.analysis.BlazeVersionInfo;
 import com.google.devtools.build.lib.bazel.BazelVersion;
+import com.google.devtools.build.lib.bazel.bzlmod.InterimModule.DepSpec;
 import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.RootModuleFileValue;
 import com.google.devtools.build.lib.bazel.bzlmod.Version.ParseException;
 import com.google.devtools.build.lib.bazel.repository.RepositoryOptions.BazelCompatibilityMode;
@@ -78,18 +80,18 @@
     if (root == null) {
       return null;
     }
-    ImmutableMap<ModuleKey, Module> initialDepGraph = Discovery.run(env, root);
+    ImmutableMap<ModuleKey, InterimModule> initialDepGraph = Discovery.run(env, root);
     if (initialDepGraph == null) {
       return null;
     }
 
-    BazelModuleResolutionValue selectionResultValue;
+    Selection.Result selectionResult;
     try {
-      selectionResultValue = Selection.run(initialDepGraph, root.getOverrides());
+      selectionResult = Selection.run(initialDepGraph, root.getOverrides());
     } catch (ExternalDepsException e) {
       throw new BazelModuleResolutionFunctionException(e, Transience.PERSISTENT);
     }
-    ImmutableMap<ModuleKey, Module> resolvedDepGraph = selectionResultValue.getResolvedDepGraph();
+    ImmutableMap<ModuleKey, InterimModule> resolvedDepGraph = selectionResult.getResolvedDepGraph();
 
     verifyRootModuleDirectDepsAreAccurate(
         initialDepGraph.get(ModuleKey.ROOT),
@@ -109,40 +111,15 @@
             Objects.requireNonNull(ALLOWED_YANKED_VERSIONS.get(env))),
         env.getListener());
 
-    // Add repo spec to each module and remove registry
-    try {
-      ImmutableMap.Builder<ModuleKey, Module> mapBuilder = ImmutableMap.builder();
-      for (Map.Entry<ModuleKey, Module> entry : resolvedDepGraph.entrySet()) {
-        Module module = entry.getValue();
-        // Only change modules with registry (not overridden)
-        if (module.getRegistry() != null) {
-          RepoSpec moduleRepoSpec =
-              module
-                  .getRegistry()
-                  .getRepoSpec(module.getKey(), module.getCanonicalRepoName(), env.getListener());
-          ModuleOverride override = root.getOverrides().get(entry.getKey().getName());
-          moduleRepoSpec = maybeAppendAdditionalPatches(moduleRepoSpec, override);
-          module = module.toBuilder().setRepoSpec(moduleRepoSpec).setRegistry(null).build();
-        }
-        mapBuilder.put(entry.getKey(), module);
-      }
-      resolvedDepGraph = mapBuilder.buildOrThrow();
-    } catch (IOException e) {
-      throw new BazelModuleResolutionFunctionException(
-          ExternalDepsException.withMessage(
-              Code.ERROR_ACCESSING_REGISTRY,
-              "Unable to get module repo spec from registry: %s",
-              e.getMessage()),
-          Transience.PERSISTENT);
-    }
+    ImmutableMap<ModuleKey, Module> finalDepGraph =
+        computeFinalDepGraph(resolvedDepGraph, root.getOverrides(), env.getListener());
 
-    return BazelModuleResolutionValue.create(
-        resolvedDepGraph, selectionResultValue.getUnprunedDepGraph());
+    return BazelModuleResolutionValue.create(finalDepGraph, selectionResult.getUnprunedDepGraph());
   }
 
   private static void verifyRootModuleDirectDepsAreAccurate(
-      Module discoveredRootModule,
-      Module resolvedRootModule,
+      InterimModule discoveredRootModule,
+      InterimModule resolvedRootModule,
       CheckDirectDepsMode mode,
       EventHandler eventHandler)
       throws BazelModuleResolutionFunctionException {
@@ -151,14 +128,14 @@
     }
 
     boolean failure = false;
-    for (Map.Entry<String, ModuleKey> dep : discoveredRootModule.getDeps().entrySet()) {
-      ModuleKey resolved = resolvedRootModule.getDeps().get(dep.getKey());
-      if (!dep.getValue().equals(resolved)) {
+    for (Map.Entry<String, DepSpec> dep : discoveredRootModule.getDeps().entrySet()) {
+      ModuleKey resolved = resolvedRootModule.getDeps().get(dep.getKey()).toModuleKey();
+      if (!dep.getValue().toModuleKey().equals(resolved)) {
         String message =
             String.format(
                 "For repository '%s', the root module requires module version %s, but got %s in the"
                     + " resolved dependency graph.",
-                dep.getKey(), dep.getValue(), resolved);
+                dep.getKey(), dep.getValue().toModuleKey(), resolved);
         if (mode == CheckDirectDepsMode.WARNING) {
           eventHandler.handle(Event.warn(message));
         } else {
@@ -177,7 +154,9 @@
   }
 
   public static void checkBazelCompatibility(
-      ImmutableCollection<Module> modules, BazelCompatibilityMode mode, EventHandler eventHandler)
+      ImmutableCollection<InterimModule> modules,
+      BazelCompatibilityMode mode,
+      EventHandler eventHandler)
       throws BazelModuleResolutionFunctionException {
     if (mode == BazelCompatibilityMode.OFF) {
       return;
@@ -189,7 +168,7 @@
     }
 
     BazelVersion curVersion = BazelVersion.parse(currentBazelVersion);
-    for (Module module : modules) {
+    for (InterimModule module : modules) {
       for (String compatVersion : module.getBazelCompatibility()) {
         if (!curVersion.satisfiesCompatibility(compatVersion)) {
           String message =
@@ -306,14 +285,14 @@
     return false;
   }
 
-  private void verifyYankedVersions(
-      ImmutableMap<ModuleKey, Module> depGraph,
+  private static void verifyYankedVersions(
+      ImmutableMap<ModuleKey, InterimModule> depGraph,
       Optional<ImmutableSet<ModuleKey>> allowedYankedVersions,
       ExtendedEventHandler eventHandler)
       throws BazelModuleResolutionFunctionException, InterruptedException {
     // Check whether all resolved modules are either not yanked or allowed. Modules with a
     // NonRegistryOverride are ignored as their metadata is not available whatsoever.
-    for (Module m : depGraph.values()) {
+    for (InterimModule m : depGraph.values()) {
       if (m.getKey().equals(ModuleKey.ROOT) || m.getRegistry() == null) {
         continue;
       }
@@ -349,7 +328,7 @@
     }
   }
 
-  private RepoSpec maybeAppendAdditionalPatches(RepoSpec repoSpec, ModuleOverride override) {
+  private static RepoSpec maybeAppendAdditionalPatches(RepoSpec repoSpec, ModuleOverride override) {
     if (!(override instanceof SingleVersionOverride)) {
       return repoSpec;
     }
@@ -369,6 +348,66 @@
         .build();
   }
 
+  @Nullable
+  private static RepoSpec computeRepoSpec(
+      InterimModule interimModule, ModuleOverride override, ExtendedEventHandler eventHandler)
+      throws BazelModuleResolutionFunctionException, InterruptedException {
+    if (interimModule.getRegistry() == null) {
+      // This module has a non-registry override. We don't need to store the repo spec in this case.
+      return null;
+    }
+    try {
+      RepoSpec moduleRepoSpec =
+          interimModule
+              .getRegistry()
+              .getRepoSpec(
+                  interimModule.getKey(), interimModule.getCanonicalRepoName(), eventHandler);
+      return maybeAppendAdditionalPatches(moduleRepoSpec, override);
+    } catch (IOException e) {
+      throw new BazelModuleResolutionFunctionException(
+          ExternalDepsException.withMessage(
+              Code.ERROR_ACCESSING_REGISTRY,
+              "Unable to get module repo spec from registry: %s",
+              e.getMessage()),
+          Transience.PERSISTENT);
+    }
+  }
+
+  /**
+   * Builds a {@link Module} from an {@link InterimModule}, discarding unnecessary fields and adding
+   * extra necessary ones (such as the repo spec).
+   */
+  static Module moduleFromInterimModule(
+      InterimModule interim, ModuleOverride override, ExtendedEventHandler eventHandler)
+      throws BazelModuleResolutionFunctionException, InterruptedException {
+    return Module.builder()
+        .setName(interim.getName())
+        .setVersion(interim.getVersion())
+        .setKey(interim.getKey())
+        .setRepoName(interim.getRepoName())
+        .setExecutionPlatformsToRegister(interim.getExecutionPlatformsToRegister())
+        .setToolchainsToRegister(interim.getToolchainsToRegister())
+        .setDeps(ImmutableMap.copyOf(Maps.transformValues(interim.getDeps(), DepSpec::toModuleKey)))
+        .setRepoSpec(computeRepoSpec(interim, override, eventHandler))
+        .setExtensionUsages(interim.getExtensionUsages())
+        .build();
+  }
+
+  private static ImmutableMap<ModuleKey, Module> computeFinalDepGraph(
+      ImmutableMap<ModuleKey, InterimModule> resolvedDepGraph,
+      ImmutableMap<String, ModuleOverride> overrides,
+      ExtendedEventHandler eventHandler)
+      throws BazelModuleResolutionFunctionException, InterruptedException {
+    ImmutableMap.Builder<ModuleKey, Module> finalDepGraph = ImmutableMap.builder();
+    for (Map.Entry<ModuleKey, InterimModule> entry : resolvedDepGraph.entrySet()) {
+      finalDepGraph.put(
+          entry.getKey(),
+          moduleFromInterimModule(
+              entry.getValue(), overrides.get(entry.getKey().getName()), eventHandler));
+    }
+    return finalDepGraph.buildOrThrow();
+  }
+
   static class BazelModuleResolutionFunctionException extends SkyFunctionException {
     BazelModuleResolutionFunctionException(ExternalDepsException e, Transience transience) {
       super(e, transience);
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionValue.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionValue.java
index f2c7346..19c642c 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionValue.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionValue.java
@@ -44,11 +44,11 @@
    * overridden by {@code single_version_override} or {@link NonRegistryOverride}, only by {@code
    * multiple_version_override}.
    */
-  abstract ImmutableMap<ModuleKey, Module> getUnprunedDepGraph();
+  abstract ImmutableMap<ModuleKey, InterimModule> getUnprunedDepGraph();
 
   static BazelModuleResolutionValue create(
       ImmutableMap<ModuleKey, Module> resolvedDepGraph,
-      ImmutableMap<ModuleKey, Module> unprunedDepGraph) {
+      ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph) {
     return new AutoValue_BazelModuleResolutionValue(resolvedDepGraph, unprunedDepGraph);
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Discovery.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Discovery.java
index 7fb3a67..d041c3c 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Discovery.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Discovery.java
@@ -16,9 +16,9 @@
 package com.google.devtools.build.lib.bazel.bzlmod;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.bazel.bzlmod.InterimModule.DepSpec;
 import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.RootModuleFileValue;
 import com.google.devtools.build.skyframe.SkyFunction.Environment;
-import com.google.devtools.build.skyframe.SkyFunctionException;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyframeLookupResult;
 import java.util.ArrayDeque;
@@ -42,23 +42,24 @@
    * dependency is missing and this function needs a restart).
    */
   @Nullable
-  public static ImmutableMap<ModuleKey, Module> run(Environment env, RootModuleFileValue root)
-      throws SkyFunctionException, InterruptedException {
+  public static ImmutableMap<ModuleKey, InterimModule> run(
+      Environment env, RootModuleFileValue root) throws InterruptedException {
     String rootModuleName = root.getModule().getName();
     ImmutableMap<String, ModuleOverride> overrides = root.getOverrides();
-    Map<ModuleKey, Module> depGraph = new HashMap<>();
-    depGraph.put(ModuleKey.ROOT, rewriteDepKeys(root.getModule(), overrides, rootModuleName));
+    Map<ModuleKey, InterimModule> depGraph = new HashMap<>();
+    depGraph.put(ModuleKey.ROOT, rewriteDepSpecs(root.getModule(), overrides, rootModuleName));
     Queue<ModuleKey> unexpanded = new ArrayDeque<>();
     unexpanded.add(ModuleKey.ROOT);
     while (!unexpanded.isEmpty()) {
       Set<SkyKey> unexpandedSkyKeys = new HashSet<>();
       while (!unexpanded.isEmpty()) {
-        Module module = depGraph.get(unexpanded.remove());
-        for (ModuleKey depKey : module.getDeps().values()) {
-          if (depGraph.containsKey(depKey)) {
+        InterimModule module = depGraph.get(unexpanded.remove());
+        for (DepSpec depSpec : module.getDeps().values()) {
+          if (depGraph.containsKey(depSpec.toModuleKey())) {
             continue;
           }
-          unexpandedSkyKeys.add(ModuleFileValue.key(depKey, overrides.get(depKey.getName())));
+          unexpandedSkyKeys.add(
+              ModuleFileValue.key(depSpec.toModuleKey(), overrides.get(depSpec.getName())));
         }
       }
       SkyframeLookupResult result = env.getValuesAndExceptions(unexpandedSkyKeys);
@@ -70,7 +71,7 @@
           depGraph.put(depKey, null);
         } else {
           depGraph.put(
-              depKey, rewriteDepKeys(moduleFileValue.getModule(), overrides, rootModuleName));
+              depKey, rewriteDepSpecs(moduleFileValue.getModule(), overrides, rootModuleName));
           unexpanded.add(depKey);
         }
       }
@@ -81,16 +82,16 @@
     return ImmutableMap.copyOf(depGraph);
   }
 
-  private static Module rewriteDepKeys(
-      Module module, ImmutableMap<String, ModuleOverride> overrides, String rootModuleName) {
-    return module.withDepKeysTransformed(
-        depKey -> {
-          if (rootModuleName.equals(depKey.getName())) {
-            return ModuleKey.ROOT;
+  private static InterimModule rewriteDepSpecs(
+      InterimModule module, ImmutableMap<String, ModuleOverride> overrides, String rootModuleName) {
+    return module.withDepSpecsTransformed(
+        depSpec -> {
+          if (rootModuleName.equals(depSpec.getName())) {
+            return DepSpec.fromModuleKey(ModuleKey.ROOT);
           }
 
-          Version newVersion = depKey.getVersion();
-          @Nullable ModuleOverride override = overrides.get(depKey.getName());
+          Version newVersion = depSpec.getVersion();
+          @Nullable ModuleOverride override = overrides.get(depSpec.getName());
           if (override instanceof NonRegistryOverride) {
             newVersion = Version.EMPTY;
           } else if (override instanceof SingleVersionOverride) {
@@ -100,7 +101,7 @@
             }
           }
 
-          return ModuleKey.create(depKey.getName(), newVersion);
+          return DepSpec.create(depSpec.getName(), newVersion, depSpec.getMaxCompatibilityLevel());
         });
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/InterimModule.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/InterimModule.java
new file mode 100644
index 0000000..06f5051
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/InterimModule.java
@@ -0,0 +1,213 @@
+// Copyright 2023 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package com.google.devtools.build.lib.bazel.bzlmod;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.ryanharter.auto.value.gson.GenerateTypeAdapter;
+import java.util.Optional;
+import java.util.function.UnaryOperator;
+import javax.annotation.Nullable;
+
+/**
+ * Represents a node in the external dependency graph during module resolution (discovery &
+ * selection).
+ *
+ * <p>In particular, it represents a specific version of a module; there can be multiple {@link
+ * InterimModule}s in a dependency graph with the same name but with different versions (such as
+ * after discovery but before selection, or when there's a multiple_version_override in play).
+ *
+ * <p>Compared to {@link Module}, which is used after module resolution, this class holds some more
+ * information that's useful only during resolution, such as the {@code max_compatibility_level} for
+ * each dep, the {@code compatibility_level}, the {@code registry} the module comes from, etc.
+ */
+@AutoValue
+@GenerateTypeAdapter
+public abstract class InterimModule extends ModuleBase {
+
+  /**
+   * The compatibility level of the module, which essentially signifies the "major version" of the
+   * module in terms of SemVer.
+   */
+  public abstract int getCompatibilityLevel();
+
+  /** List of bazel compatible versions that would run/fail this module */
+  public abstract ImmutableList<String> getBazelCompatibility();
+
+  /** The specification of a dependency. */
+  @AutoValue
+  public abstract static class DepSpec {
+    public abstract String getName();
+
+    public abstract Version getVersion();
+
+    public abstract int getMaxCompatibilityLevel();
+
+    public static DepSpec create(String name, Version version, int maxCompatibilityLevel) {
+      return new AutoValue_InterimModule_DepSpec(name, version, maxCompatibilityLevel);
+    }
+
+    public static DepSpec fromModuleKey(ModuleKey key) {
+      return create(key.getName(), key.getVersion(), -1);
+    }
+
+    public final ModuleKey toModuleKey() {
+      return ModuleKey.create(getName(), getVersion());
+    }
+  }
+
+  /**
+   * The resolved direct dependencies of this module, which can be either the original ones,
+   * overridden by a {@code single_version_override}, by a {@code multiple_version_override}, or by
+   * a {@link NonRegistryOverride} (the version will be ""). The key type is the repo name of the
+   * dep.
+   */
+  public abstract ImmutableMap<String, DepSpec> getDeps();
+
+  /**
+   * The original direct dependencies of this module as they are declared in their MODULE file. The
+   * key type is the repo name of the dep.
+   */
+  public abstract ImmutableMap<String, DepSpec> getOriginalDeps();
+
+  /**
+   * The registry where this module came from. Must be null iff the module has a {@link
+   * NonRegistryOverride}.
+   */
+  @Nullable
+  public abstract Registry getRegistry();
+
+  /** Returns a {@link Builder} that starts out with the same fields as this object. */
+  abstract Builder toBuilder();
+
+  /** Returns a new, empty {@link Builder}. */
+  public static Builder builder() {
+    return new AutoValue_InterimModule.Builder()
+        .setName("")
+        .setVersion(Version.EMPTY)
+        .setKey(ModuleKey.ROOT)
+        .setCompatibilityLevel(0);
+  }
+
+  /**
+   * Returns a new {@link InterimModule} with all values in {@link #getDeps} transformed using the
+   * given function.
+   */
+  public InterimModule withDepSpecsTransformed(UnaryOperator<DepSpec> transform) {
+    return toBuilder()
+        .setDeps(ImmutableMap.copyOf(Maps.transformValues(getDeps(), transform::apply)))
+        .build();
+  }
+
+  /** Builder type for {@link InterimModule}. */
+  @AutoValue.Builder
+  public abstract static class Builder {
+    /** Optional; defaults to the empty string. */
+    public abstract Builder setName(String value);
+
+    /** Optional; defaults to {@link Version#EMPTY}. */
+    public abstract Builder setVersion(Version value);
+
+    /** Optional; defaults to {@link ModuleKey#ROOT}. */
+    public abstract Builder setKey(ModuleKey value);
+
+    /** Optional; defaults to {@code 0}. */
+    public abstract Builder setCompatibilityLevel(int value);
+
+    /** Optional; defaults to {@link #setName}. */
+    public abstract Builder setRepoName(String value);
+
+    public abstract Builder setBazelCompatibility(ImmutableList<String> value);
+
+    abstract ImmutableList.Builder<String> bazelCompatibilityBuilder();
+
+    @CanIgnoreReturnValue
+    public final Builder addBazelCompatibilityValues(Iterable<String> values) {
+      bazelCompatibilityBuilder().addAll(values);
+      return this;
+    }
+
+    public abstract Builder setExecutionPlatformsToRegister(ImmutableList<String> value);
+
+    abstract ImmutableList.Builder<String> executionPlatformsToRegisterBuilder();
+
+    @CanIgnoreReturnValue
+    public final Builder addExecutionPlatformsToRegister(Iterable<String> values) {
+      executionPlatformsToRegisterBuilder().addAll(values);
+      return this;
+    }
+
+    public abstract Builder setToolchainsToRegister(ImmutableList<String> value);
+
+    abstract ImmutableList.Builder<String> toolchainsToRegisterBuilder();
+
+    @CanIgnoreReturnValue
+    public final Builder addToolchainsToRegister(Iterable<String> values) {
+      toolchainsToRegisterBuilder().addAll(values);
+      return this;
+    }
+
+    public abstract Builder setOriginalDeps(ImmutableMap<String, DepSpec> value);
+
+    public abstract Builder setDeps(ImmutableMap<String, DepSpec> value);
+
+    abstract ImmutableMap.Builder<String, DepSpec> depsBuilder();
+
+    @CanIgnoreReturnValue
+    public Builder addDep(String depRepoName, DepSpec depSpec) {
+      depsBuilder().put(depRepoName, depSpec);
+      return this;
+    }
+
+    abstract ImmutableMap.Builder<String, DepSpec> originalDepsBuilder();
+
+    @CanIgnoreReturnValue
+    public Builder addOriginalDep(String depRepoName, DepSpec depSpec) {
+      originalDepsBuilder().put(depRepoName, depSpec);
+      return this;
+    }
+
+    public abstract Builder setRegistry(Registry value);
+
+    public abstract Builder setExtensionUsages(ImmutableList<ModuleExtensionUsage> value);
+
+    abstract ImmutableList.Builder<ModuleExtensionUsage> extensionUsagesBuilder();
+
+    @CanIgnoreReturnValue
+    public Builder addExtensionUsage(ModuleExtensionUsage value) {
+      extensionUsagesBuilder().add(value);
+      return this;
+    }
+
+    abstract ModuleKey getKey();
+
+    abstract String getName();
+
+    abstract Optional<String> getRepoName();
+
+    abstract InterimModule autoBuild();
+
+    final InterimModule build() {
+      if (getRepoName().isEmpty()) {
+        setRepoName(getName());
+      }
+      return autoBuild();
+    }
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Module.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Module.java
index 86fae25..3616636 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Module.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Module.java
@@ -18,104 +18,33 @@
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
 import com.google.devtools.build.lib.cmdline.RepositoryMapping;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.ryanharter.auto.value.gson.GenerateTypeAdapter;
 import java.util.Map;
-import java.util.Optional;
-import java.util.function.UnaryOperator;
 import javax.annotation.Nullable;
 
 /**
  * Represents a node in the external dependency graph.
  *
  * <p>In particular, it represents a specific version of a module; there can be multiple {@link
- * Module}s in a dependency graph with the same name but with different versions (such as after
- * discovery but before selection, or when there's a multiple_version_override in play).
+ * Module}s in a dependency graph with the same name but with different versions (when there's a
+ * multiple_version_override in play).
+ *
+ * <p>For the intermediate type used during module resolution, see {@link InterimModule}.
  */
 @AutoValue
 @GenerateTypeAdapter
-public abstract class Module {
+public abstract class Module extends ModuleBase {
 
   /**
-   * The name of the module, as specified in this module's MODULE.bazel file. Can be empty if this
-   * is the root module.
-   */
-  public abstract String getName();
-
-  /**
-   * The version of the module, as specified in this module's MODULE.bazel file. Can be empty if
-   * this is the root module, or if this module comes from a {@link NonRegistryOverride}.
-   */
-  public abstract Version getVersion();
-
-  /**
-   * The key of this module in the dependency graph. Note that, although a {@link ModuleKey} is also
-   * just a (name, version) pair, its semantics differ from {@link #getName} and {@link
-   * #getVersion}, which are always as specified in the MODULE.bazel file. The {@link ModuleKey}
-   * returned by this method, however, will have the following special semantics:
-   *
-   * <ul>
-   *   <li>The name of the {@link ModuleKey} is the same as {@link #getName}, unless this is the
-   *       root module, in which case the name of the {@link ModuleKey} must be empty.
-   *   <li>The version of the {@link ModuleKey} is the same as {@link #getVersion}, unless this is
-   *       the root module OR this module has a {@link NonRegistryOverride}, in which case the
-   *       version of the {@link ModuleKey} must be empty.
-   * </ul>
-   */
-  public abstract ModuleKey getKey();
-
-  public final RepositoryName getCanonicalRepoName() {
-    return getKey().getCanonicalRepoName();
-  }
-
-  /**
-   * The compatibility level of the module, which essentially signifies the "major version" of the
-   * module in terms of SemVer.
-   */
-  public abstract int getCompatibilityLevel();
-
-  /**
-   * The name of the repository representing this module, as seen by the module itself. By default,
-   * the name of the repo is the name of the module. This can be specified to ease migration for
-   * projects that have been using a repo name for itself that differs from its module name.
-   */
-  public abstract String getRepoName();
-
-  /** List of bazel compatible versions that would run/fail this module */
-  public abstract ImmutableList<String> getBazelCompatibility();
-
-  /**
-   * Target patterns identifying execution platforms to register when this module is selected. Note
-   * that these are what was written in module files verbatim, and don't contain canonical repo
-   * names.
-   */
-  public abstract ImmutableList<String> getExecutionPlatformsToRegister();
-
-  /**
-   * Target patterns identifying toolchains to register when this module is selected. Note that
-   * these are what was written in module files verbatim, and don't contain canonical repo names.
-   */
-  public abstract ImmutableList<String> getToolchainsToRegister();
-
-  /**
-   * The resolved direct dependencies of this module, which can be either the original ones,
-   * overridden by a {@code single_version_override}, by a {@code multiple_version_override}, or by
-   * a {@link NonRegistryOverride} (the version will be ""). The key type is the repo name of the
-   * dep, and the value type is the ModuleKey (name+version) of the dep.
+   * The resolved direct dependencies of this module. The key type is the repo name of the dep, and
+   * the value type is the ModuleKey ({@link #getKey()}) of the dep.
    */
   public abstract ImmutableMap<String, ModuleKey> getDeps();
 
   /**
-   * The original direct dependencies of this module as they are declared in their MODULE file. The
-   * key type is the repo name of the dep, and the value type is the ModuleKey (name+version) of the
-   * dep.
-   */
-  public abstract ImmutableMap<String, ModuleKey> getOriginalDeps();
-
-  /**
    * Returns a {@link RepositoryMapping} with only Bazel module repos and no repos from module
    * extensions. For the full mapping, see {@link BazelDepGraphValue#getFullRepoMapping}.
    */
@@ -138,97 +67,33 @@
     return RepositoryMapping.create(mapping.buildOrThrow(), getCanonicalRepoName());
   }
 
-  // TODO(salmasamy) create two modules (One with registry, one with repospec and only necessary
-  // things for lockfile)
   /**
-   * The registry where this module came from. Must be null iff the module has a {@link
-   * NonRegistryOverride}. Set to null after running selection and verifying yanked versions.
-   */
-  @Nullable
-  public abstract Registry getRegistry();
-
-  /**
-   * The repo spec for this module (information about the attributes of its repository rule) Filled
-   * after running selection to avoid extra calls to the registry.
+   * The repo spec for this module (information about the attributes of its repository rule). This
+   * is only non-null for modules coming from registries (i.e. without non-registry overrides).
    */
   @Nullable
   public abstract RepoSpec getRepoSpec();
 
-  /** The module extensions used in this module. */
-  public abstract ImmutableList<ModuleExtensionUsage> getExtensionUsages();
-
-  /** Returns a {@link Builder} that starts out with the same fields as this object. */
-  abstract Builder toBuilder();
-
   /** Returns a new, empty {@link Builder}. */
   public static Builder builder() {
-    return new AutoValue_Module.Builder()
-        .setName("")
-        .setVersion(Version.EMPTY)
-        .setKey(ModuleKey.ROOT)
-        .setCompatibilityLevel(0);
-  }
-
-  /**
-   * Returns a new {@link Module} with all values in {@link #getDeps} transformed using the given
-   * function.
-   */
-  public Module withDepKeysTransformed(UnaryOperator<ModuleKey> transform) {
-    return toBuilder()
-        .setDeps(ImmutableMap.copyOf(Maps.transformValues(getDeps(), transform::apply)))
-        .build();
+    return new AutoValue_Module.Builder();
   }
 
   /** Builder type for {@link Module}. */
   @AutoValue.Builder
   public abstract static class Builder {
-    /** Optional; defaults to the empty string. */
     public abstract Builder setName(String value);
 
-    /** Optional; defaults to {@link Version#EMPTY}. */
     public abstract Builder setVersion(Version value);
 
-    /** Optional; defaults to {@link ModuleKey#ROOT}. */
     public abstract Builder setKey(ModuleKey value);
 
-    /** Optional; defaults to {@code 0}. */
-    public abstract Builder setCompatibilityLevel(int value);
-
-    /** Optional; defaults to {@link #setName}. */
     public abstract Builder setRepoName(String value);
 
-    public abstract Builder setBazelCompatibility(ImmutableList<String> value);
-
-    abstract ImmutableList.Builder<String> bazelCompatibilityBuilder();
-
-    @CanIgnoreReturnValue
-    public final Builder addBazelCompatibilityValues(Iterable<String> values) {
-      bazelCompatibilityBuilder().addAll(values);
-      return this;
-    }
-
     public abstract Builder setExecutionPlatformsToRegister(ImmutableList<String> value);
 
-    abstract ImmutableList.Builder<String> executionPlatformsToRegisterBuilder();
-
-    @CanIgnoreReturnValue
-    public final Builder addExecutionPlatformsToRegister(Iterable<String> values) {
-      executionPlatformsToRegisterBuilder().addAll(values);
-      return this;
-    }
-
     public abstract Builder setToolchainsToRegister(ImmutableList<String> value);
 
-    abstract ImmutableList.Builder<String> toolchainsToRegisterBuilder();
-
-    @CanIgnoreReturnValue
-    public final Builder addToolchainsToRegister(Iterable<String> values) {
-      toolchainsToRegisterBuilder().addAll(values);
-      return this;
-    }
-
-    public abstract Builder setOriginalDeps(ImmutableMap<String, ModuleKey> value);
-
     public abstract Builder setDeps(ImmutableMap<String, ModuleKey> value);
 
     abstract ImmutableMap.Builder<String, ModuleKey> depsBuilder();
@@ -239,16 +104,6 @@
       return this;
     }
 
-    abstract ImmutableMap.Builder<String, ModuleKey> originalDepsBuilder();
-
-    @CanIgnoreReturnValue
-    public Builder addOriginalDep(String depRepoName, ModuleKey depKey) {
-      originalDepsBuilder().put(depRepoName, depKey);
-      return this;
-    }
-
-    public abstract Builder setRegistry(Registry value);
-
     public abstract Builder setRepoSpec(RepoSpec value);
 
     public abstract Builder setExtensionUsages(ImmutableList<ModuleExtensionUsage> value);
@@ -261,19 +116,6 @@
       return this;
     }
 
-    abstract ModuleKey getKey();
-
-    abstract String getName();
-
-    abstract Optional<String> getRepoName();
-
-    abstract Module autoBuild();
-
-    final Module build() {
-      if (getRepoName().isEmpty()) {
-        setRepoName(getName());
-      }
-      return autoBuild();
-    }
+    abstract Module build();
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleBase.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleBase.java
new file mode 100644
index 0000000..67e8d37
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleBase.java
@@ -0,0 +1,78 @@
+// Copyright 2023 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package com.google.devtools.build.lib.bazel.bzlmod;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
+
+/** Represents a node in the external dependency graph. */
+abstract class ModuleBase {
+
+  /**
+   * The name of the module, as specified in this module's MODULE.bazel file. Can be empty if this
+   * is the root module.
+   */
+  public abstract String getName();
+
+  /**
+   * The version of the module, as specified in this module's MODULE.bazel file. Can be empty if
+   * this is the root module, or if this module comes from a {@link NonRegistryOverride}.
+   */
+  public abstract Version getVersion();
+
+  /**
+   * The key of this module in the dependency graph. Note that, although a {@link ModuleKey} is also
+   * just a (name, version) pair, its semantics differ from {@link #getName} and {@link
+   * #getVersion}, which are always as specified in the MODULE.bazel file. The {@link ModuleKey}
+   * returned by this method, however, will have the following special semantics:
+   *
+   * <ul>
+   *   <li>The name of the {@link ModuleKey} is the same as {@link #getName}, unless this is the
+   *       root module, in which case the name of the {@link ModuleKey} must be empty.
+   *   <li>The version of the {@link ModuleKey} is the same as {@link #getVersion}, unless this is
+   *       the root module OR this module has a {@link NonRegistryOverride}, in which case the
+   *       version of the {@link ModuleKey} must be empty.
+   * </ul>
+   */
+  public abstract ModuleKey getKey();
+
+  public final RepositoryName getCanonicalRepoName() {
+    return getKey().getCanonicalRepoName();
+  }
+
+  /**
+   * The name of the repository representing this module, as seen by the module itself. By default,
+   * the name of the repo is the name of the module. This can be specified to ease migration for
+   * projects that have been using a repo name for itself that differs from its module name.
+   */
+  public abstract String getRepoName();
+
+  /**
+   * Target patterns identifying execution platforms to register when this module is selected. Note
+   * that these are what was written in module files verbatim, and don't contain canonical repo
+   * names.
+   */
+  public abstract ImmutableList<String> getExecutionPlatformsToRegister();
+
+  /**
+   * Target patterns identifying toolchains to register when this module is selected. Note that
+   * these are what was written in module files verbatim, and don't contain canonical repo names.
+   */
+  public abstract ImmutableList<String> getToolchainsToRegister();
+
+  /** The module extensions used in this module. */
+  public abstract ImmutableList<ModuleExtensionUsage> getExtensionUsages();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunction.java
index d971460..18efa92 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunction.java
@@ -123,7 +123,7 @@
             env);
 
     // Perform some sanity checks.
-    Module module = moduleFileGlobals.buildModule();
+    InterimModule module = moduleFileGlobals.buildModule();
     if (!module.getName().equals(moduleKey.getName())) {
       throw errorf(
           Code.BAD_MODULE,
@@ -159,12 +159,12 @@
     ModuleFileGlobals moduleFileGlobals =
         execModuleFile(
             moduleFile,
-            /*registry=*/ null,
+            /* registry= */ null,
             ModuleKey.ROOT,
             /* ignoreDevDeps= */ Objects.requireNonNull(IGNORE_DEV_DEPS.get(env)),
             starlarkSemantics,
             env);
-    Module module = moduleFileGlobals.buildModule();
+    InterimModule module = moduleFileGlobals.buildModule();
 
     ImmutableMap<String, ModuleOverride> moduleOverrides = moduleFileGlobals.buildOverrides();
     Map<String, ModuleOverride> commandOverrides = MODULE_OVERRIDES.get(env);
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java
index a141e2c..c2800b9 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java
@@ -26,6 +26,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.docgen.annot.DocumentMethods;
+import com.google.devtools.build.lib.bazel.bzlmod.InterimModule.DepSpec;
 import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileGlobals.ModuleExtensionUsageBuilder.ModuleExtensionProxy;
 import com.google.devtools.build.lib.bazel.bzlmod.Version.ParseException;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
@@ -64,8 +65,8 @@
   private boolean moduleCalled = false;
   private boolean hadNonModuleCall = false;
   private final boolean ignoreDevDeps;
-  private final Module.Builder module;
-  private final Map<String, ModuleKey> deps = new LinkedHashMap<>();
+  private final InterimModule.Builder module;
+  private final Map<String, DepSpec> deps = new LinkedHashMap<>();
   private final List<ModuleExtensionUsageBuilder> extensionUsageBuilders = new ArrayList<>();
   private final Map<String, ModuleOverride> overrides = new HashMap<>();
   private final Map<String, RepoNameUsage> repoNameUsages = new HashMap<>();
@@ -75,7 +76,7 @@
       ModuleKey key,
       @Nullable Registry registry,
       boolean ignoreDevDeps) {
-    module = Module.builder().setKey(key).setRegistry(registry);
+    module = InterimModule.builder().setKey(key).setRegistry(registry);
     this.ignoreDevDeps = ignoreDevDeps;
     if (ModuleKey.ROOT.equals(key)) {
       overrides.putAll(builtinModules);
@@ -85,7 +86,7 @@
         // The built-in module does not depend on itself.
         continue;
       }
-      deps.put(builtinModule, ModuleKey.create(builtinModule, Version.EMPTY));
+      deps.put(builtinModule, DepSpec.create(builtinModule, Version.EMPTY, -1));
       try {
         addRepoNameUsage(builtinModule, "as a built-in dependency", Location.BUILTIN);
       } catch (EvalException e) {
@@ -281,6 +282,16 @@
             positional = false,
             defaultValue = "''"),
         @Param(
+            name = "max_compatibility_level",
+            doc =
+                "The maximum <code>compatibility_level</code> supported for the module to be added"
+                    + " as a direct dependency. The version of the module implies the minimum"
+                    + " compatibility_level supported, as well as the maximum if this attribute is"
+                    + " not specified.",
+            named = true,
+            positional = false,
+            defaultValue = "-1"),
+        @Param(
             name = "repo_name",
             doc =
                 "The name of the external repo representing this dependency. This is by default the"
@@ -299,7 +310,12 @@
       },
       useStarlarkThread = true)
   public void bazelDep(
-      String name, String version, String repoName, boolean devDependency, StarlarkThread thread)
+      String name,
+      String version,
+      StarlarkInt maxCompatibilityLevel,
+      String repoName,
+      boolean devDependency,
+      StarlarkThread thread)
       throws EvalException {
     hadNonModuleCall = true;
     if (repoName.isEmpty()) {
@@ -315,7 +331,10 @@
     RepositoryName.validateUserProvidedRepoName(repoName);
 
     if (!(ignoreDevDeps && devDependency)) {
-      deps.put(repoName, ModuleKey.create(name, parsedVersion));
+      deps.put(
+          repoName,
+          DepSpec.create(
+              name, parsedVersion, maxCompatibilityLevel.toInt("max_compatibility_level")));
     }
 
     addRepoNameUsage(repoName, "by a bazel_dep", thread.getCallerLocation());
@@ -879,7 +898,7 @@
     addOverride(moduleName, LocalPathOverride.create(path));
   }
 
-  public Module buildModule() {
+  public InterimModule buildModule() {
     return module
         .setDeps(ImmutableMap.copyOf(deps))
         .setOriginalDeps(ImmutableMap.copyOf(deps))
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileValue.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileValue.java
index 48d47e8e..01628b8 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileValue.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileValue.java
@@ -38,7 +38,7 @@
    * module might not match the one in the requesting {@link SkyKey} in certain circumstances (for
    * example, for the root module, or when non-registry overrides are in play.
    */
-  public abstract Module getModule();
+  public abstract InterimModule getModule();
 
   /** The hash string of Module.bazel (using SHA256) */
   public abstract String getModuleFileHash();
@@ -47,7 +47,7 @@
   @AutoValue
   public abstract static class NonRootModuleFileValue extends ModuleFileValue {
 
-    public static NonRootModuleFileValue create(Module module, String moduleFileHash) {
+    public static NonRootModuleFileValue create(InterimModule module, String moduleFileHash) {
       return new AutoValue_ModuleFileValue_NonRootModuleFileValue(module, moduleFileHash);
     }
   }
@@ -72,7 +72,7 @@
         getNonRegistryOverrideCanonicalRepoNameLookup();
 
     public static RootModuleFileValue create(
-        Module module,
+        InterimModule module,
         String moduleFileHash,
         ImmutableMap<String, ModuleOverride> overrides,
         ImmutableMap<RepositoryName, String> nonRegistryOverrideCanonicalRepoNameLookup) {
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Selection.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Selection.java
index bcacb7b..361f523 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Selection.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Selection.java
@@ -15,6 +15,10 @@
 
 package com.google.devtools.build.lib.bazel.bzlmod;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
+import static java.util.Comparator.naturalOrder;
+
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
@@ -22,14 +26,19 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.devtools.build.lib.bazel.bzlmod.InterimModule.DepSpec;
 import com.google.devtools.build.lib.server.FailureDetails.ExternalDeps.Code;
 import java.util.ArrayDeque;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
+import java.util.function.Function;
 import javax.annotation.Nullable;
 
 /**
@@ -62,11 +71,41 @@
  *       be removed before the end of selection (by becoming unreachable, for example), otherwise
  *       it'll be an error since they're not allowed by the override (these versions are in
  *       selection groups that have no valid target allowed version).
+ *   <li>Things get even more complicated with max_compatibility_level. The difference this
+ *       introduces is that each "DepSpec" could be satisfied by one of multiple choices. (Without
+ *       max_compatibility_level, there is always only one choice.) So what we do is go through all
+ *       the combinations of possible choices for each distinct DepSpec, and for each combination,
+ *       see if the resulting dep graph is valid. As soon as we find a valid combination, we return
+ *       that result. The distinct DepSpecs are sorted by the order they first appear in the dep
+ *       graph if we BFS from the root module. The combinations are attempted in the typical
+ *       cartesian product order (see {@link Lists#cartesianProduct}); the "version choices" of each
+ *       DepSpec are sorted from low to high.
  * </ul>
  */
 final class Selection {
   private Selection() {}
 
+  /** The result of selection. */
+  @AutoValue
+  abstract static class Result {
+    /** Final dep graph sorted in BFS iteration order, with unused modules removed. */
+    abstract ImmutableMap<ModuleKey, InterimModule> getResolvedDepGraph();
+
+    /**
+     * Un-pruned dep graph, with updated dep keys, and additionally containing the unused modules
+     * which were initially discovered (and their MODULE.bazel files loaded). Does not contain
+     * modules overridden by {@code single_version_override} or {@link NonRegistryOverride}, only by
+     * {@code multiple_version_override}.
+     */
+    abstract ImmutableMap<ModuleKey, InterimModule> getUnprunedDepGraph();
+
+    static Result create(
+        ImmutableMap<ModuleKey, InterimModule> resolvedDepGraph,
+        ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph) {
+      return new AutoValue_Selection_Result(resolvedDepGraph, unprunedDepGraph);
+    }
+  }
+
   /** During selection, a version is selected for each distinct "selection group". */
   @AutoValue
   abstract static class SelectionGroup {
@@ -102,7 +141,8 @@
    */
   private static ImmutableMap<ModuleNameAndCompatibilityLevel, ImmutableSortedSet<Version>>
       computeAllowedVersionSets(
-          ImmutableMap<String, ModuleOverride> overrides, ImmutableMap<ModuleKey, Module> depGraph)
+          ImmutableMap<String, ModuleOverride> overrides,
+          ImmutableMap<ModuleKey, InterimModule> depGraph)
           throws ExternalDepsException {
     Map<ModuleNameAndCompatibilityLevel, ImmutableSortedSet.Builder<Version>> allowedVersionSets =
         new HashMap<>();
@@ -114,7 +154,8 @@
       }
       ImmutableList<Version> allowedVersions = ((MultipleVersionOverride) override).getVersions();
       for (Version allowedVersion : allowedVersions) {
-        Module allowedVersionModule = depGraph.get(ModuleKey.create(moduleName, allowedVersion));
+        InterimModule allowedVersionModule =
+            depGraph.get(ModuleKey.create(moduleName, allowedVersion));
         if (allowedVersionModule == null) {
           throw ExternalDepsException.withMessage(
               Code.VERSION_RESOLUTION_ERROR,
@@ -143,7 +184,7 @@
    * used to compute its targetAllowedVersion.
    */
   private static SelectionGroup computeSelectionGroup(
-      Module module,
+      InterimModule module,
       ImmutableMap<ModuleNameAndCompatibilityLevel, ImmutableSortedSet<Version>>
           allowedVersionSets) {
     ImmutableSortedSet<Version> allowedVersionSet =
@@ -152,10 +193,11 @@
                 module.getName(), module.getCompatibilityLevel()));
     if (allowedVersionSet == null) {
       // This means that this module has no multiple-version override.
-      return SelectionGroup.create(module.getName(), module.getCompatibilityLevel(), Version.EMPTY);
+      return SelectionGroup.create(
+          module.getKey().getName(), module.getCompatibilityLevel(), Version.EMPTY);
     }
     return SelectionGroup.create(
-        module.getName(),
+        module.getKey().getName(),
         module.getCompatibilityLevel(),
         // We use the `ceiling` method here to quickly locate the lowest allowed version that's
         // still no lower than this module's version.
@@ -166,10 +208,94 @@
   }
 
   /**
-   * Runs module selection (aka version resolution). Returns a {@link BazelModuleResolutionValue}.
+   * Computes the possible list of ModuleKeys a single given DepSpec can resolve to. This is
+   * normally just one ModuleKey, but when max_compatibility_level is involved, multiple choices may
+   * be possible.
    */
-  public static BazelModuleResolutionValue run(
-      ImmutableMap<ModuleKey, Module> depGraph, ImmutableMap<String, ModuleOverride> overrides)
+  private static ImmutableList<ModuleKey> computePossibleResolutionResultsForOneDepSpec(
+      DepSpec depSpec,
+      ImmutableMap<ModuleKey, SelectionGroup> selectionGroups,
+      Map<SelectionGroup, Version> selectedVersions) {
+    int minCompatibilityLevel = selectionGroups.get(depSpec.toModuleKey()).getCompatibilityLevel();
+    int maxCompatibilityLevel =
+        depSpec.getMaxCompatibilityLevel() < 0
+            ? minCompatibilityLevel
+            : depSpec.getMaxCompatibilityLevel();
+    // First find the selection groups that this DepSpec could use.
+    return Maps.filterKeys(
+            selectedVersions,
+            group ->
+                group.getModuleName().equals(depSpec.getName())
+                    && group.getCompatibilityLevel() >= minCompatibilityLevel
+                    && group.getCompatibilityLevel() <= maxCompatibilityLevel
+                    && group.getTargetAllowedVersion().compareTo(depSpec.getVersion()) >= 0)
+        .entrySet()
+        .stream()
+        // Collect into an ImmutableSortedMap so that:
+        //  1. The final list is sorted by compatibility level, guaranteeing lowest version first;
+        //  2. Only one ModuleKey is attempted per compatibility level, so that in the case of a
+        //     multiple-version override, we only try the lowest allowed version in that
+        //     compatibility level (note the Comparators::min call).
+        .collect(
+            toImmutableSortedMap(
+                naturalOrder(),
+                e -> e.getKey().getCompatibilityLevel(),
+                e -> e.getValue(),
+                Comparators::min))
+        .values()
+        .stream()
+        .map(v -> ModuleKey.create(depSpec.getName(), v))
+        .collect(toImmutableList());
+  }
+
+  /**
+   * Computes the possible list of ModuleKeys a DepSpec can resolve to, for all distinct DepSpecs in
+   * the dependency graph.
+   */
+  private static ImmutableMap<DepSpec, ImmutableList<ModuleKey>> computePossibleResolutionResults(
+      ImmutableMap<ModuleKey, InterimModule> depGraph,
+      ImmutableMap<ModuleKey, SelectionGroup> selectionGroups,
+      Map<SelectionGroup, Version> selectedVersions) {
+    // Important that we use a LinkedHashMap here to ensure reproducibility.
+    Map<DepSpec, ImmutableList<ModuleKey>> results = new LinkedHashMap<>();
+    for (InterimModule module : depGraph.values()) {
+      for (DepSpec depSpec : module.getDeps().values()) {
+        results.computeIfAbsent(
+            depSpec,
+            ds ->
+                computePossibleResolutionResultsForOneDepSpec(
+                    ds, selectionGroups, selectedVersions));
+      }
+    }
+    return ImmutableMap.copyOf(results);
+  }
+
+  /**
+   * Given the possible list of ModuleKeys each DepSpec can resolve to, enumerate through all the
+   * possible resolution strategies. Each strategy assigns each DepSpec to a single ModuleKey out of
+   * its possible list.
+   */
+  private static List<Function<DepSpec, ModuleKey>> enumerateStrategies(
+      ImmutableMap<DepSpec, ImmutableList<ModuleKey>> possibleResolutionResults) {
+    Map<DepSpec, Integer> depSpecToPosition = new HashMap<>();
+    int position = 0;
+    for (DepSpec depSpec : possibleResolutionResults.keySet()) {
+      depSpecToPosition.put(depSpec, position++);
+    }
+    return Lists.transform(
+        Lists.cartesianProduct(possibleResolutionResults.values().asList()),
+        (List<ModuleKey> choices) ->
+            (DepSpec depSpec) -> choices.get(depSpecToPosition.get(depSpec)));
+    // TODO(wyv): There are some strategies that we could eliminate earlier. For example, the
+    //   strategy where (foo@1.1, maxCL=3) resolves to foo@2.0 and (foo@1.2, maxCL=3) resolves to
+    //   foo@3.0 is obviously not valid. All foo@? should resolve to the same version (assuming no
+    //   multiple-version override).
+  }
+
+  /** Runs module selection (aka version resolution). */
+  public static Result run(
+      ImmutableMap<ModuleKey, InterimModule> depGraph,
+      ImmutableMap<String, ModuleOverride> overrides)
       throws ExternalDepsException {
     // For any multiple-version overrides, build a mapping from (moduleName, compatibilityLevel) to
     // the set of allowed versions.
@@ -193,42 +319,55 @@
       selectedVersions.merge(selectionGroup, key.getVersion(), Comparators::max);
     }
 
-    // Build a new dep graph where deps with unselected versions are removed.
-    ImmutableMap.Builder<ModuleKey, Module> newDepGraphBuilder = new ImmutableMap.Builder<>();
-
-    // Also keep a version of the full dep graph with updated deps.
-    ImmutableMap.Builder<ModuleKey, Module> unprunedDepGraphBuilder = new ImmutableMap.Builder<>();
-    for (Module module : depGraph.values()) {
-      // Rewrite deps to point to the selected version.
-      ModuleKey key = module.getKey();
-      Module updatedModule =
-          module.withDepKeysTransformed(
-              depKey ->
-                  ModuleKey.create(
-                      depKey.getName(), selectedVersions.get(selectionGroups.get(depKey))));
-
-      // Add all updated modules to the un-pruned dep graph.
-      unprunedDepGraphBuilder.put(key, updatedModule);
-
-      // Remove any dep whose version isn't selected from the resolved graph.
-      Version selectedVersion = selectedVersions.get(selectionGroups.get(module.getKey()));
-      if (module.getKey().getVersion().equals(selectedVersion)) {
-        newDepGraphBuilder.put(key, updatedModule);
+    // Compute the possible list of ModuleKeys that each DepSpec could resolve to.
+    ImmutableMap<DepSpec, ImmutableList<ModuleKey>> possibleResolutionResults =
+        computePossibleResolutionResults(depGraph, selectionGroups, selectedVersions);
+    for (Map.Entry<DepSpec, ImmutableList<ModuleKey>> e : possibleResolutionResults.entrySet()) {
+      if (e.getValue().isEmpty()) {
+        throw ExternalDepsException.withMessage(
+            Code.VERSION_RESOLUTION_ERROR,
+            "Unexpected error: %s has no valid resolution result",
+            e.getKey());
       }
     }
-    ImmutableMap<ModuleKey, Module> newDepGraph = newDepGraphBuilder.buildOrThrow();
-    ImmutableMap<ModuleKey, Module> unprunedDepGraph = unprunedDepGraphBuilder.buildOrThrow();
 
-    // Further, removes unreferenced modules from the graph. We can find out which modules are
-    // referenced by collecting deps transitively from the root.
-    // We can also take this opportunity to check that none of the remaining modules conflict with
-    // each other (e.g. same module name but different compatibility levels, or not satisfying
-    // multiple_version_override).
-    ImmutableMap<ModuleKey, Module> prunedDepGraph =
-        new DepGraphWalker(newDepGraph, overrides, selectionGroups).walk();
-
-    // Return the result containing both the pruned and un-pruned dep graphs
-    return BazelModuleResolutionValue.create(prunedDepGraph, unprunedDepGraph);
+    // Each DepSpec may resolve to one or more ModuleKeys. We try out every single possible
+    // combination; in other words, we enumerate through the cartesian product of the "possible
+    // resolution result" set for every distinct DepSpec. Each element of this cartesian product is
+    // essentially a mapping from DepSpecs to ModuleKeys; we can call this mapping a "resolution
+    // strategy".
+    //
+    // Given a resolution strategy, we can walk through the graph from the root module, and see if
+    // the strategy yields a valid graph (only containing the nodes reachable from the root). If the
+    // graph is invalid (for example, because there are modules with different compatibility
+    // levels), we try the next resolution strategy. When all strategies are exhausted, we know
+    // there is no way to achieve a valid selection result, so we report the failure from the time
+    // we attempted to walk the graph using the first resolution strategy.
+    DepGraphWalker depGraphWalker = new DepGraphWalker(depGraph, overrides, selectionGroups);
+    ExternalDepsException firstFailure = null;
+    for (Function<DepSpec, ModuleKey> resolutionStrategy :
+        enumerateStrategies(possibleResolutionResults)) {
+      try {
+        ImmutableMap<ModuleKey, InterimModule> prunedDepGraph =
+            depGraphWalker.walk(resolutionStrategy);
+        // If the call above didn't throw, we have a valid graph. Go ahead and produce a result!
+        ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph =
+            ImmutableMap.copyOf(
+                Maps.transformValues(
+                    depGraph,
+                    module ->
+                        module.withDepSpecsTransformed(
+                            depSpec -> DepSpec.fromModuleKey(resolutionStrategy.apply(depSpec)))));
+        return Result.create(prunedDepGraph, unprunedDepGraph);
+      } catch (ExternalDepsException e) {
+        if (firstFailure == null) {
+          firstFailure = e;
+        }
+      }
+    }
+    // firstFailure cannot be null, since enumerateStrategies(...) cannot be empty, since no
+    // element of possibleResolutionResults is empty.
+    throw firstFailure;
   }
 
   /**
@@ -237,27 +376,27 @@
    */
   static class DepGraphWalker {
     private static final Joiner JOINER = Joiner.on(", ");
-    private final ImmutableMap<ModuleKey, Module> oldDepGraph;
+    private final ImmutableMap<ModuleKey, InterimModule> oldDepGraph;
     private final ImmutableMap<String, ModuleOverride> overrides;
     private final ImmutableMap<ModuleKey, SelectionGroup> selectionGroups;
-    private final HashMap<String, ExistingModule> moduleByName;
 
     DepGraphWalker(
-        ImmutableMap<ModuleKey, Module> oldDepGraph,
+        ImmutableMap<ModuleKey, InterimModule> oldDepGraph,
         ImmutableMap<String, ModuleOverride> overrides,
         ImmutableMap<ModuleKey, SelectionGroup> selectionGroups) {
       this.oldDepGraph = oldDepGraph;
       this.overrides = overrides;
       this.selectionGroups = selectionGroups;
-      this.moduleByName = new HashMap<>();
     }
 
     /**
      * Walks the old dep graph and builds a new dep graph containing only deps reachable from the
      * root module. The returned map has a guaranteed breadth-first iteration order.
      */
-    ImmutableMap<ModuleKey, Module> walk() throws ExternalDepsException {
-      ImmutableMap.Builder<ModuleKey, Module> newDepGraph = ImmutableMap.builder();
+    ImmutableMap<ModuleKey, InterimModule> walk(Function<DepSpec, ModuleKey> resolutionStrategy)
+        throws ExternalDepsException {
+      HashMap<String, ExistingModule> moduleByName = new HashMap<>();
+      ImmutableMap.Builder<ModuleKey, InterimModule> newDepGraph = ImmutableMap.builder();
       Set<ModuleKey> known = new HashSet<>();
       Queue<ModuleKeyAndDependent> toVisit = new ArrayDeque<>();
       toVisit.add(ModuleKeyAndDependent.create(ModuleKey.ROOT, null));
@@ -265,12 +404,16 @@
       while (!toVisit.isEmpty()) {
         ModuleKeyAndDependent moduleKeyAndDependent = toVisit.remove();
         ModuleKey key = moduleKeyAndDependent.getModuleKey();
-        Module module = oldDepGraph.get(key);
-        visit(key, module, moduleKeyAndDependent.getDependent());
+        InterimModule module =
+            oldDepGraph
+                .get(key)
+                .withDepSpecsTransformed(
+                    depSpec -> DepSpec.fromModuleKey(resolutionStrategy.apply(depSpec)));
+        visit(key, module, moduleKeyAndDependent.getDependent(), moduleByName);
 
-        for (ModuleKey depKey : module.getDeps().values()) {
-          if (known.add(depKey)) {
-            toVisit.add(ModuleKeyAndDependent.create(depKey, key));
+        for (DepSpec depSpec : module.getDeps().values()) {
+          if (known.add(depSpec.toModuleKey())) {
+            toVisit.add(ModuleKeyAndDependent.create(depSpec.toModuleKey(), key));
           }
         }
         newDepGraph.put(key, module);
@@ -278,7 +421,11 @@
       return newDepGraph.buildOrThrow();
     }
 
-    void visit(ModuleKey key, Module module, @Nullable ModuleKey from)
+    void visit(
+        ModuleKey key,
+        InterimModule module,
+        @Nullable ModuleKey from,
+        HashMap<String, ExistingModule> moduleByName)
         throws ExternalDepsException {
       ModuleOverride override = overrides.get(key.getName());
       if (override instanceof MultipleVersionOverride) {
@@ -321,10 +468,10 @@
 
       // Make sure that we don't have `module` depending on the same dependency version twice.
       HashMap<ModuleKey, String> depKeyToRepoName = new HashMap<>();
-      for (Map.Entry<String, ModuleKey> depEntry : module.getDeps().entrySet()) {
+      for (Map.Entry<String, DepSpec> depEntry : module.getDeps().entrySet()) {
         String repoName = depEntry.getKey();
-        ModuleKey depKey = depEntry.getValue();
-        String previousRepoName = depKeyToRepoName.put(depKey, repoName);
+        DepSpec depSpec = depEntry.getValue();
+        String previousRepoName = depKeyToRepoName.put(depSpec.toModuleKey(), repoName);
         if (previousRepoName != null) {
           throw ExternalDepsException.withMessage(
               Code.VERSION_RESOLUTION_ERROR,
@@ -332,7 +479,7 @@
                   + " multiple_version_override if you want to depend on multiple versions of"
                   + " %s simultaneously",
               key,
-              depKey,
+              depSpec.toModuleKey(),
               repoName,
               previousRepoName,
               key.getName());
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BzlmodRepoRuleFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/BzlmodRepoRuleFunction.java
index e4942e3..a7c5c48 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BzlmodRepoRuleFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BzlmodRepoRuleFunction.java
@@ -14,7 +14,6 @@
 
 package com.google.devtools.build.lib.skyframe;
 
-import static com.google.common.base.Preconditions.checkNotNull;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -163,9 +162,7 @@
     if (moduleKey == null) {
       return Optional.empty();
     }
-    com.google.devtools.build.lib.bazel.bzlmod.Module module =
-        bazelDepGraphValue.getDepGraph().get(moduleKey);
-    return Optional.of(checkNotNull(module.getRepoSpec()));
+    return Optional.of(bazelDepGraphValue.getDepGraph().get(moduleKey).getRepoSpec());
   }
 
   @Nullable
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelDepGraphFunctionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelDepGraphFunctionTest.java
index b1979e9..3e10c00 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelDepGraphFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelDepGraphFunctionTest.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.buildModule;
 import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createModuleKey;
 import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createRepositoryMapping;
 import static org.junit.Assert.fail;
@@ -162,9 +163,7 @@
         ImmutableMap.<ModuleKey, Module>builder()
             .put(
                 ModuleKey.ROOT,
-                Module.builder()
-                    .setName("my_root")
-                    .setVersion(Version.parse("1.0"))
+                buildModule("my_root", "1.0")
                     .setKey(ModuleKey.ROOT)
                     .addDep("my_dep_1", createModuleKey("dep", "1.0"))
                     .addDep("my_dep_2", createModuleKey("dep", "2.0"))
@@ -172,33 +171,14 @@
                     .build())
             .put(
                 createModuleKey("dep", "1.0"),
-                Module.builder()
-                    .setName("dep")
-                    .setVersion(Version.parse("1.0"))
-                    .setKey(createModuleKey("dep", "1.0"))
+                buildModule("dep", "1.0")
                     .addDep("rules_java", createModuleKey("rules_java", ""))
                     .build())
-            .put(
-                createModuleKey("dep", "2.0"),
-                Module.builder()
-                    .setName("dep")
-                    .setVersion(Version.parse("2.0"))
-                    .setKey(createModuleKey("dep", "2.0"))
-                    .build())
-            .put(
-                createModuleKey("rules_cc", "1.0"),
-                Module.builder()
-                    .setName("rules_cc")
-                    .setVersion(Version.parse("1.0"))
-                    .setKey(createModuleKey("rules_cc", "1.0"))
-                    .build())
+            .put(createModuleKey("dep", "2.0"), buildModule("dep", "2.0").build())
+            .put(createModuleKey("rules_cc", "1.0"), buildModule("rules_cc", "1.0").build())
             .put(
                 createModuleKey("rules_java", ""),
-                Module.builder()
-                    .setName("rules_java")
-                    .setVersion(Version.parse("1.0"))
-                    .setKey(createModuleKey("rules_java", ""))
-                    .build())
+                buildModule("rules_java", "1.0").setKey(createModuleKey("rules_java", "")).build())
             .buildOrThrow();
 
     resolutionFunctionMock.setDepGraph(depGraph);
@@ -248,9 +228,7 @@
         "module(name='my_root', version='1.0')");
 
     Module root =
-        Module.builder()
-            .setName("root")
-            .setVersion(Version.parse("1.0"))
+        buildModule("root", "1.0")
             .setKey(ModuleKey.ROOT)
             .addDep("rje", createModuleKey("rules_jvm_external", "1.0"))
             .addDep("rpy", createModuleKey("rules_python", "2.0"))
@@ -261,9 +239,7 @@
             .build();
     ModuleKey depKey = createModuleKey("dep", "2.0");
     Module dep =
-        Module.builder()
-            .setName("dep")
-            .setVersion(Version.parse("2.0"))
+        buildModule("dep", "2.0")
             .setKey(depKey)
             .addDep("rules_python", createModuleKey("rules_python", "2.0"))
             .addExtensionUsage(
@@ -352,7 +328,7 @@
         "module(name='module', version='1.0')");
 
     Module root =
-        Module.builder()
+        buildModule("module", "1.0")
             .addExtensionUsage(createModuleExtensionUsage("@foo//:defs.bzl", "bar"))
             .build();
     ImmutableMap<ModuleKey, Module> depGraph = ImmutableMap.of(ModuleKey.ROOT, root);
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileFunctionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileFunctionTest.java
index 9a20ae0..e10d52d 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileFunctionTest.java
@@ -67,7 +67,6 @@
 import com.google.devtools.build.skyframe.SkyFunctionName;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
-import java.io.IOException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import javax.annotation.Nullable;
@@ -221,9 +220,10 @@
     RootModuleFileValue rootValue = rootResult.get(ModuleFileValue.KEY_FOR_ROOT_MODULE);
 
     ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
-            .put(ModuleKey.ROOT, rootValue.getModule())
-            .buildOrThrow();
+        ImmutableMap.of(
+            ModuleKey.ROOT,
+            BazelModuleResolutionFunction.moduleFromInterimModule(
+                rootValue.getModule(), null, null));
 
     UpdateLockFileKey key =
         UpdateLockFileKey.create("moduleHash", depGraph, rootValue.getOverrides());
@@ -259,9 +259,10 @@
     RootModuleFileValue rootValue = rootResult.get(ModuleFileValue.KEY_FOR_ROOT_MODULE);
 
     ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
-            .put(ModuleKey.ROOT, rootValue.getModule())
-            .buildOrThrow();
+        ImmutableMap.of(
+            ModuleKey.ROOT,
+            BazelModuleResolutionFunction.moduleFromInterimModule(
+                rootValue.getModule(), null, null));
 
     ImmutableList<String> yankedVersions = ImmutableList.of("2.4", "2.3");
     LocalPathOverride override = LocalPathOverride.create("override_path");
@@ -300,7 +301,7 @@
   }
 
   @Test
-  public void moduleWithLocalOverrides() throws IOException, InterruptedException {
+  public void moduleWithLocalOverrides() throws Exception {
     scratch.file(
         rootDirectory.getRelative("MODULE.bazel").getPathString(),
         "module(name='root',version='0.1')",
@@ -319,9 +320,10 @@
     RootModuleFileValue rootValue = rootResult.get(ModuleFileValue.KEY_FOR_ROOT_MODULE);
 
     ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
-            .put(ModuleKey.ROOT, rootValue.getModule())
-            .buildOrThrow();
+        ImmutableMap.of(
+            ModuleKey.ROOT,
+            BazelModuleResolutionFunction.moduleFromInterimModule(
+                rootValue.getModule(), null, null));
 
     UpdateLockFileKey key =
         UpdateLockFileKey.create("moduleHash", depGraph, rootValue.getOverrides());
@@ -363,9 +365,10 @@
     RootModuleFileValue rootValue = rootResult.get(ModuleFileValue.KEY_FOR_ROOT_MODULE);
 
     ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
-            .put(ModuleKey.ROOT, rootValue.getModule())
-            .buildOrThrow();
+        ImmutableMap.of(
+            ModuleKey.ROOT,
+            BazelModuleResolutionFunction.moduleFromInterimModule(
+                rootValue.getModule(), null, null));
 
     UpdateLockFileKey key =
         UpdateLockFileKey.create("moduleHash", depGraph, rootValue.getOverrides());
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleInspectorFunctionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleInspectorFunctionTest.java
index ce2027c..1fb7765 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleInspectorFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleInspectorFunctionTest.java
@@ -24,7 +24,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue.AugmentedModule;
 import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue.AugmentedModule.ResolutionReason;
-import com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.ModuleBuilder;
+import com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.InterimModuleBuilder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -35,25 +35,25 @@
 
   @Test
   public void testDiamond_simple() throws Exception {
-    ImmutableMap<ModuleKey, Module> unprunedDepGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
                     .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0")
+                InterimModuleBuilder.create("bbb", "1.0")
                     .addDep("ddd_from_bbb", createModuleKey("ddd", "2.0"))
                     .addOriginalDep("ddd_from_bbb", createModuleKey("ddd", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("ccc", "2.0")
+                InterimModuleBuilder.create("ccc", "2.0")
                     .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ddd", "1.0", 1).buildEntry())
-            .put(ModuleBuilder.create("ddd", "2.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "2.0", 1).buildEntry())
             .buildOrThrow();
 
     ImmutableSet<ModuleKey> usedModules =
@@ -91,29 +91,29 @@
 
   @Test
   public void testDiamond_withFurtherRemoval() throws Exception {
-    ImmutableMap<ModuleKey, Module> unprunedDepGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb", createModuleKey("bbb", "1.0"))
                     .addDep("ccc", createModuleKey("ccc", "2.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0")
+                InterimModuleBuilder.create("bbb", "1.0")
                     .addDep("ddd", createModuleKey("ddd", "2.0"))
                     .addOriginalDep("ddd", createModuleKey("ddd", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("ccc", "2.0")
+                InterimModuleBuilder.create("ccc", "2.0")
                     .addDep("ddd", createModuleKey("ddd", "2.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ddd", "2.0").buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "2.0").buildEntry())
             .put(
-                ModuleBuilder.create("ddd", "1.0")
+                InterimModuleBuilder.create("ddd", "1.0")
                     .addDep("eee", createModuleKey("eee", "1.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("eee", "1.0").buildEntry())
+            .put(InterimModuleBuilder.create("eee", "1.0").buildEntry())
             .buildOrThrow();
 
     ImmutableSet<ModuleKey> usedModules =
@@ -154,27 +154,27 @@
 
   @Test
   public void testCircularDependencyDueToSelection() throws Exception {
-    ImmutableMap<ModuleKey, Module> unprunedDepGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb", createModuleKey("bbb", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0")
+                InterimModuleBuilder.create("bbb", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "2.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("ccc", "2.0")
+                InterimModuleBuilder.create("ccc", "2.0")
                     .addDep("bbb", createModuleKey("bbb", "1.0"))
                     .addOriginalDep("bbb", createModuleKey("bbb", "1.0-pre"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0-pre")
+                InterimModuleBuilder.create("bbb", "1.0-pre")
                     .addDep("ddd", createModuleKey("ddd", "1.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ddd", "1.0").buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "1.0").buildEntry())
             .buildOrThrow();
 
     ImmutableSet<ModuleKey> usedModules =
@@ -210,23 +210,23 @@
     // single_version_override (ccc, 2.0)
     // aaa -> bbb 1.0 -> ccc 1.0 -> ddd 1.0
     //                   ccc 2.0 -> ddd 2.0
-    ImmutableMap<ModuleKey, Module> unprunedDepGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb", createModuleKey("bbb", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0")
+                InterimModuleBuilder.create("bbb", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "2.0"))
                     .addOriginalDep("ccc", createModuleKey("ccc", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("ccc", "2.0")
+                InterimModuleBuilder.create("ccc", "2.0")
                     .addDep("ddd", createModuleKey("ddd", "2.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ddd", "2.0").buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "2.0").buildEntry())
             .buildOrThrow();
 
     ImmutableMap<String, ModuleOverride> overrides =
@@ -271,20 +271,20 @@
     // archive_override "file://users/user/bbb.zip"
     // aaa    -> bbb 1.0        -> ccc 1.0 (not loaded)
     //   (local) bbb 1.0-hotfix -> ccc 1.1
-    ImmutableMap<ModuleKey, Module> unprunedDepGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb", createModuleKey("bbb", ""))
                     .addOriginalDep("bbb", createModuleKey("bbb", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0")
+                InterimModuleBuilder.create("bbb", "1.0")
                     .setKey(createModuleKey("bbb", ""))
                     .addDep("ccc", createModuleKey("ccc", "1.1"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.1").buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.1").buildEntry())
             .buildOrThrow();
 
     ImmutableMap<String, ModuleOverride> overrides =
@@ -328,27 +328,27 @@
     //     \-> ccc 2.0
     // multiple_version_override ccc: [1.5, 2.0]
     // multiple_version_override bbb: [1.0, 2.0]
-    ImmutableMap<ModuleKey, Module> unprunedDepGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb1", createModuleKey("bbb", "1.0"))
                     .addDep("bbb2", createModuleKey("bbb", "2.0"))
                     .addDep("ccc", createModuleKey("ccc", "2.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0")
+                InterimModuleBuilder.create("bbb", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "1.5"))
                     .addOriginalDep("ccc", createModuleKey("ccc", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "2.0")
+                InterimModuleBuilder.create("bbb", "2.0")
                     .addDep("ccc", createModuleKey("ccc", "1.5"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.0").buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.5").buildEntry())
-            .put(ModuleBuilder.create("ccc", "2.0").buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.0").buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.5").buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "2.0").buildEntry())
             .buildOrThrow();
 
     ImmutableMap<String, ModuleOverride> overrides =
@@ -412,10 +412,10 @@
     //     \            \-> bbb4@1.1
     //     \-> bbb4@1.1
     // ccc@1.5 and ccc@3.0, the versions violating the allowlist, are gone.
-    ImmutableMap<ModuleKey, Module> unprunedDepGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb1", createModuleKey("bbb1", "1.0"))
                     .addDep("bbb2", createModuleKey("bbb2", "1.1"))
@@ -425,29 +425,29 @@
                     .addOriginalDep("bbb4", createModuleKey("bbb4", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb1", "1.0")
+                InterimModuleBuilder.create("bbb1", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "1.0"))
                     .addDep("bbb2", createModuleKey("bbb2", "1.1"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb2", "1.0")
+                InterimModuleBuilder.create("bbb2", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "1.5"))
                     .buildEntry())
-            .put(ModuleBuilder.create("bbb2", "1.1").buildEntry())
+            .put(InterimModuleBuilder.create("bbb2", "1.1").buildEntry())
             .put(
-                ModuleBuilder.create("bbb3", "1.0")
+                InterimModuleBuilder.create("bbb3", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "2.0"))
                     .addDep("bbb4", createModuleKey("bbb4", "1.1"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb4", "1.0")
+                InterimModuleBuilder.create("bbb4", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "3.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("bbb4", "1.1").buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.0", 1).buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.5", 1).buildEntry())
-            .put(ModuleBuilder.create("ccc", "2.0", 2).buildEntry())
-            .put(ModuleBuilder.create("ccc", "3.0", 3).buildEntry())
+            .put(InterimModuleBuilder.create("bbb4", "1.1").buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.5", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "2.0", 2).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "3.0", 3).buildEntry())
             .buildOrThrow();
 
     ImmutableMap<String, ModuleOverride> overrides =
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BzlmodTestUtil.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BzlmodTestUtil.java
index 484e40e..fcd4913 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BzlmodTestUtil.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BzlmodTestUtil.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue.AugmentedModule;
 import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue.AugmentedModule.ResolutionReason;
+import com.google.devtools.build.lib.bazel.bzlmod.InterimModule.DepSpec;
 import com.google.devtools.build.lib.bazel.bzlmod.Version.ParseException;
 import com.google.devtools.build.lib.cmdline.RepositoryMapping;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
@@ -43,21 +44,41 @@
     }
   }
 
+  public static DepSpec createDepSpec(String name, String version, int maxCompatibilityLevel) {
+    try {
+      return DepSpec.create(name, Version.parse(version), maxCompatibilityLevel);
+    } catch (Version.ParseException e) {
+      throw new IllegalArgumentException(e);
+    }
+  }
+
+  public static Module.Builder buildModule(String name, String version) throws Exception {
+    return Module.builder()
+        .setName(name)
+        .setVersion(Version.parse(version))
+        .setRepoName(name)
+        .setKey(createModuleKey(name, version))
+        .setExtensionUsages(ImmutableList.of())
+        .setExecutionPlatformsToRegister(ImmutableList.of())
+        .setToolchainsToRegister(ImmutableList.of());
+  }
+
   /** Builder class to create a {@code Entry<ModuleKey, Module>} entry faster inside UnitTests */
-  static final class ModuleBuilder {
-    Module.Builder builder;
+  static final class InterimModuleBuilder {
+    InterimModule.Builder builder;
     ModuleKey key;
-    ImmutableMap.Builder<String, ModuleKey> deps = new ImmutableMap.Builder<>();
-    ImmutableMap.Builder<String, ModuleKey> originalDeps = new ImmutableMap.Builder<>();
+    ImmutableMap.Builder<String, DepSpec> deps = new ImmutableMap.Builder<>();
+    ImmutableMap.Builder<String, DepSpec> originalDeps = new ImmutableMap.Builder<>();
 
-    private ModuleBuilder() {}
+    private InterimModuleBuilder() {}
 
-    public static ModuleBuilder create(String name, Version version, int compatibilityLevel) {
-      ModuleBuilder moduleBuilder = new ModuleBuilder();
+    public static InterimModuleBuilder create(
+        String name, Version version, int compatibilityLevel) {
+      InterimModuleBuilder moduleBuilder = new InterimModuleBuilder();
       ModuleKey key = ModuleKey.create(name, version);
       moduleBuilder.key = key;
       moduleBuilder.builder =
-          Module.builder()
+          InterimModule.builder()
               .setName(name)
               .setVersion(version)
               .setKey(key)
@@ -65,84 +86,96 @@
       return moduleBuilder;
     }
 
-    public static ModuleBuilder create(String name, String version, int compatibilityLevel)
+    public static InterimModuleBuilder create(String name, String version, int compatibilityLevel)
         throws ParseException {
       return create(name, Version.parse(version), compatibilityLevel);
     }
 
-    public static ModuleBuilder create(String name, String version) throws ParseException {
+    public static InterimModuleBuilder create(String name, String version) throws ParseException {
       return create(name, Version.parse(version), 0);
     }
 
-    public static ModuleBuilder create(String name, Version version) throws ParseException {
+    public static InterimModuleBuilder create(String name, Version version) throws ParseException {
       return create(name, version, 0);
     }
 
     @CanIgnoreReturnValue
-    public ModuleBuilder addDep(String depRepoName, ModuleKey key) {
-      deps.put(depRepoName, key);
+    public InterimModuleBuilder addDep(String depRepoName, ModuleKey key) {
+      deps.put(depRepoName, DepSpec.fromModuleKey(key));
       return this;
     }
 
     @CanIgnoreReturnValue
-    public ModuleBuilder addOriginalDep(String depRepoName, ModuleKey key) {
-      originalDeps.put(depRepoName, key);
+    public InterimModuleBuilder addDep(String depRepoName, DepSpec depSpec) {
+      deps.put(depRepoName, depSpec);
       return this;
     }
 
     @CanIgnoreReturnValue
-    public ModuleBuilder setKey(ModuleKey value) {
+    public InterimModuleBuilder addOriginalDep(String depRepoName, ModuleKey key) {
+      originalDeps.put(depRepoName, DepSpec.fromModuleKey(key));
+      return this;
+    }
+
+    @CanIgnoreReturnValue
+    public InterimModuleBuilder addOriginalDep(String depRepoName, DepSpec depSpec) {
+      originalDeps.put(depRepoName, depSpec);
+      return this;
+    }
+
+    @CanIgnoreReturnValue
+    public InterimModuleBuilder setKey(ModuleKey value) {
       this.key = value;
       this.builder.setKey(value);
       return this;
     }
 
     @CanIgnoreReturnValue
-    public ModuleBuilder setRepoName(String value) {
+    public InterimModuleBuilder setRepoName(String value) {
       this.builder.setRepoName(value);
       return this;
     }
 
     @CanIgnoreReturnValue
-    public ModuleBuilder setRegistry(FakeRegistry value) {
+    public InterimModuleBuilder setRegistry(FakeRegistry value) {
       this.builder.setRegistry(value);
       return this;
     }
 
     @CanIgnoreReturnValue
-    public ModuleBuilder addExecutionPlatformsToRegister(ImmutableList<String> value) {
+    public InterimModuleBuilder addExecutionPlatformsToRegister(ImmutableList<String> value) {
       this.builder.addExecutionPlatformsToRegister(value);
       return this;
     }
 
     @CanIgnoreReturnValue
-    public ModuleBuilder addToolchainsToRegister(ImmutableList<String> value) {
+    public InterimModuleBuilder addToolchainsToRegister(ImmutableList<String> value) {
       this.builder.addToolchainsToRegister(value);
       return this;
     }
 
     @CanIgnoreReturnValue
-    public ModuleBuilder addExtensionUsage(ModuleExtensionUsage value) {
+    public InterimModuleBuilder addExtensionUsage(ModuleExtensionUsage value) {
       this.builder.addExtensionUsage(value);
       return this;
     }
 
-    public Map.Entry<ModuleKey, Module> buildEntry() {
-      Module module = this.build();
+    public Map.Entry<ModuleKey, InterimModule> buildEntry() {
+      InterimModule module = this.build();
       return new SimpleEntry<>(this.key, module);
     }
 
-    public Module build() {
-      ImmutableMap<String, ModuleKey> builtDeps = this.deps.buildOrThrow();
+    public InterimModule build() {
+      ImmutableMap<String, DepSpec> builtDeps = this.deps.buildOrThrow();
 
       /* Copy dep entries that have not been changed to original deps */
-      ImmutableMap<String, ModuleKey> initOriginalDeps = this.originalDeps.buildOrThrow();
-      for (Entry<String, ModuleKey> e : builtDeps.entrySet()) {
+      ImmutableMap<String, DepSpec> initOriginalDeps = this.originalDeps.buildOrThrow();
+      for (Entry<String, DepSpec> e : builtDeps.entrySet()) {
         if (!initOriginalDeps.containsKey(e.getKey())) {
           originalDeps.put(e);
         }
       }
-      ImmutableMap<String, ModuleKey> builtOriginalDeps = this.originalDeps.buildOrThrow();
+      ImmutableMap<String, DepSpec> builtOriginalDeps = this.originalDeps.buildOrThrow();
 
       return this.builder.setDeps(builtDeps).setOriginalDeps(builtOriginalDeps).build();
     }
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/DiscoveryTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/DiscoveryTest.java
index 94fa9f1..de1f279 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/DiscoveryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/DiscoveryTest.java
@@ -29,7 +29,7 @@
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
 import com.google.devtools.build.lib.analysis.ServerDirectories;
 import com.google.devtools.build.lib.analysis.util.AnalysisMock;
-import com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.ModuleBuilder;
+import com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.InterimModuleBuilder;
 import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.RootModuleFileValue;
 import com.google.devtools.build.lib.bazel.repository.starlark.StarlarkRepositoryModule;
 import com.google.devtools.build.lib.clock.BlazeClock;
@@ -91,11 +91,11 @@
     static final SkyFunctionName FUNCTION_NAME = SkyFunctionName.createHermetic("test_discovery");
     static final SkyKey KEY = () -> FUNCTION_NAME;
 
-    static DiscoveryValue create(ImmutableMap<ModuleKey, Module> depGraph) {
+    static DiscoveryValue create(ImmutableMap<ModuleKey, InterimModule> depGraph) {
       return new AutoValue_DiscoveryTest_DiscoveryValue(depGraph);
     }
 
-    abstract ImmutableMap<ModuleKey, Module> getDepGraph();
+    abstract ImmutableMap<ModuleKey, InterimModule> getDepGraph();
   }
 
   static class DiscoveryFunction implements SkyFunction {
@@ -107,7 +107,7 @@
       if (root == null) {
         return null;
       }
-      ImmutableMap<ModuleKey, Module> depGraph = Discovery.run(env, root);
+      ImmutableMap<ModuleKey, InterimModule> depGraph = Discovery.run(env, root);
       return depGraph == null ? null : DiscoveryValue.create(depGraph);
     }
   }
@@ -226,20 +226,20 @@
     DiscoveryValue discoveryValue = result.get(DiscoveryValue.KEY);
     assertThat(discoveryValue.getDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", "0.1")
+            InterimModuleBuilder.create("aaa", "0.1")
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "1.0"))
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .addDep("ddd", createModuleKey("ddd", "3.0"))
                 .setRegistry(registry)
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "2.0")
+            InterimModuleBuilder.create("ccc", "2.0")
                 .addDep("ddd", createModuleKey("ddd", "3.0"))
                 .setRegistry(registry)
                 .buildEntry(),
-            ModuleBuilder.create("ddd", "3.0").setRegistry(registry).buildEntry());
+            InterimModuleBuilder.create("ddd", "3.0").setRegistry(registry).buildEntry());
   }
 
   @Test
@@ -268,13 +268,13 @@
     DiscoveryValue discoveryValue = result.get(DiscoveryValue.KEY);
     assertThat(discoveryValue.getDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", "0.1")
+            InterimModuleBuilder.create("aaa", "0.1")
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "1.0"))
                 .addDep("ccc", createModuleKey("ccc", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0").setRegistry(registry).buildEntry(),
-            ModuleBuilder.create("ccc", "1.0").setRegistry(registry).buildEntry());
+            InterimModuleBuilder.create("bbb", "1.0").setRegistry(registry).buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.0").setRegistry(registry).buildEntry());
   }
 
   @Test
@@ -304,11 +304,11 @@
     DiscoveryValue discoveryValue = result.get(DiscoveryValue.KEY);
     assertThat(discoveryValue.getDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", "0.1")
+            InterimModuleBuilder.create("aaa", "0.1")
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0").setRegistry(registry).buildEntry());
+            InterimModuleBuilder.create("bbb", "1.0").setRegistry(registry).buildEntry());
   }
 
   @Test
@@ -336,15 +336,15 @@
     DiscoveryValue discoveryValue = result.get(DiscoveryValue.KEY);
     assertThat(discoveryValue.getDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", "0.1")
+            InterimModuleBuilder.create("aaa", "0.1")
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .setRegistry(registry)
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "2.0")
+            InterimModuleBuilder.create("ccc", "2.0")
                 .addDep("bbb", createModuleKey("bbb", "1.0"))
                 .setRegistry(registry)
                 .buildEntry());
@@ -373,11 +373,11 @@
     DiscoveryValue discoveryValue = result.get(DiscoveryValue.KEY);
     assertThat(discoveryValue.getDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", "0.1")
+            InterimModuleBuilder.create("aaa", "0.1")
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .addDep("aaa", ModuleKey.ROOT)
                 .addOriginalDep("aaa", createModuleKey("aaa", "2.0"))
                 .setRegistry(registry)
@@ -409,16 +409,16 @@
     DiscoveryValue discoveryValue = result.get(DiscoveryValue.KEY);
     assertThat(discoveryValue.getDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", "0.1")
+            InterimModuleBuilder.create("aaa", "0.1")
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "0.1"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "0.1")
+            InterimModuleBuilder.create("bbb", "0.1")
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .addOriginalDep("ccc", createModuleKey("ccc", "1.0"))
                 .setRegistry(registry)
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "2.0").setRegistry(registry).buildEntry());
+            InterimModuleBuilder.create("ccc", "2.0").setRegistry(registry).buildEntry());
   }
 
   @Test
@@ -451,15 +451,15 @@
     DiscoveryValue discoveryValue = result.get(DiscoveryValue.KEY);
     assertThat(discoveryValue.getDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", "0.1")
+            InterimModuleBuilder.create("aaa", "0.1")
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "0.1"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "0.1")
+            InterimModuleBuilder.create("bbb", "0.1")
                 .addDep("ccc", createModuleKey("ccc", "1.0"))
                 .setRegistry(registry1)
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "1.0")
+            InterimModuleBuilder.create("ccc", "1.0")
                 .addDep("bbb", createModuleKey("bbb", "0.1"))
                 .setRegistry(registry2)
                 .buildEntry());
@@ -493,16 +493,18 @@
     DiscoveryValue discoveryValue = result.get(DiscoveryValue.KEY);
     assertThat(discoveryValue.getDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", "0.1")
+            InterimModuleBuilder.create("aaa", "0.1")
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "0.1"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "0.1")
+            InterimModuleBuilder.create("bbb", "0.1")
                 .addDep("ccc", createModuleKey("ccc", ""))
                 .addOriginalDep("ccc", createModuleKey("ccc", "1.0"))
                 .setRegistry(registry)
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "2.0").setKey(createModuleKey("ccc", "")).buildEntry());
+            InterimModuleBuilder.create("ccc", "2.0")
+                .setKey(createModuleKey("ccc", ""))
+                .buildEntry());
   }
 
   @Test
@@ -541,26 +543,26 @@
     DiscoveryValue discoveryValue = result.get(DiscoveryValue.KEY);
     assertThat(discoveryValue.getDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("", "")
+            InterimModuleBuilder.create("", "")
                 .addDep("bazel_tools", createModuleKey("bazel_tools", ""))
                 .addDep("local_config_platform", createModuleKey("local_config_platform", ""))
                 .addDep("foo", createModuleKey("foo", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bazel_tools", "1.0")
+            InterimModuleBuilder.create("bazel_tools", "1.0")
                 .setKey(createModuleKey("bazel_tools", ""))
                 .addDep("local_config_platform", createModuleKey("local_config_platform", ""))
                 .addDep("foo", createModuleKey("foo", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("local_config_platform", "")
+            InterimModuleBuilder.create("local_config_platform", "")
                 .setKey(createModuleKey("local_config_platform", ""))
                 .addDep("bazel_tools", createModuleKey("bazel_tools", ""))
                 .buildEntry(),
-            ModuleBuilder.create("foo", "1.0")
+            InterimModuleBuilder.create("foo", "1.0")
                 .addDep("bazel_tools", createModuleKey("bazel_tools", ""))
                 .addDep("local_config_platform", createModuleKey("local_config_platform", ""))
                 .setRegistry(registry)
                 .buildEntry(),
-            ModuleBuilder.create("foo", "2.0")
+            InterimModuleBuilder.create("foo", "2.0")
                 .addDep("bazel_tools", createModuleKey("bazel_tools", ""))
                 .addDep("local_config_platform", createModuleKey("local_config_platform", ""))
                 .setRegistry(registry)
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/InterimModuleTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/InterimModuleTest.java
new file mode 100644
index 0000000..dd65628
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/InterimModuleTest.java
@@ -0,0 +1,51 @@
+// Copyright 2023 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License
+
+package com.google.devtools.build.lib.bazel.bzlmod;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createModuleKey;
+
+import com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.InterimModuleBuilder;
+import com.google.devtools.build.lib.bazel.bzlmod.InterimModule.DepSpec;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link InterimModule}. */
+@RunWith(JUnit4.class)
+public class InterimModuleTest {
+
+  @Test
+  public void withDepSpecsTransformed() throws Exception {
+    assertThat(
+            InterimModuleBuilder.create("", "")
+                .addDep("dep_foo", createModuleKey("foo", "1.0"))
+                .addDep("dep_bar", createModuleKey("bar", "2.0"))
+                .build()
+                .withDepSpecsTransformed(
+                    depSpec ->
+                        DepSpec.fromModuleKey(
+                            createModuleKey(
+                                depSpec.getName() + "_new",
+                                depSpec.getVersion().getOriginal() + ".1"))))
+        .isEqualTo(
+            InterimModuleBuilder.create("", "")
+                .addDep("dep_foo", createModuleKey("foo_new", "1.0.1"))
+                .addOriginalDep("dep_foo", createModuleKey("foo", "1.0"))
+                .addDep("dep_bar", createModuleKey("bar_new", "2.0.1"))
+                .addOriginalDep("dep_bar", createModuleKey("bar", "2.0"))
+                .build());
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunctionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunctionTest.java
index d55e990..d654cd8 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunctionTest.java
@@ -30,7 +30,7 @@
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
 import com.google.devtools.build.lib.analysis.ServerDirectories;
 import com.google.devtools.build.lib.analysis.util.AnalysisMock;
-import com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.ModuleBuilder;
+import com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.InterimModuleBuilder;
 import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.RootModuleFileValue;
 import com.google.devtools.build.lib.bazel.repository.starlark.StarlarkRepositoryModule;
 import com.google.devtools.build.lib.clock.BlazeClock;
@@ -202,7 +202,7 @@
     RootModuleFileValue rootModuleFileValue = result.get(ModuleFileValue.KEY_FOR_ROOT_MODULE);
     assertThat(rootModuleFileValue.getModule())
         .isEqualTo(
-            ModuleBuilder.create("aaa", "0.1", 4)
+            InterimModuleBuilder.create("aaa", "0.1", 4)
                 .setKey(ModuleKey.ROOT)
                 .addExecutionPlatformsToRegister(
                     ImmutableList.of("//my:platform", "//my:platform2"))
@@ -250,7 +250,7 @@
     RootModuleFileValue rootModuleFileValue = result.get(ModuleFileValue.KEY_FOR_ROOT_MODULE);
     assertThat(rootModuleFileValue.getModule())
         .isEqualTo(
-            ModuleBuilder.create("", "")
+            InterimModuleBuilder.create("", "")
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "1.0"))
                 .build());
@@ -303,7 +303,7 @@
     ModuleFileValue moduleFileValue = result.get(skyKey);
     assertThat(moduleFileValue.getModule())
         .isEqualTo(
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .setRegistry(registry2)
                 .build());
@@ -341,7 +341,7 @@
     ModuleFileValue moduleFileValue = result.get(skyKey);
     assertThat(moduleFileValue.getModule())
         .isEqualTo(
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .setKey(createModuleKey("bbb", ""))
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .build());
@@ -392,7 +392,7 @@
     ModuleFileValue moduleFileValue = result.get(skyKey);
     assertThat(moduleFileValue.getModule())
         .isEqualTo(
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .setKey(createModuleKey("bbb", ""))
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .build());
@@ -430,7 +430,7 @@
     ModuleFileValue moduleFileValue = result.get(skyKey);
     assertThat(moduleFileValue.getModule())
         .isEqualTo(
-            ModuleBuilder.create("bbb", "1.0", 6)
+            InterimModuleBuilder.create("bbb", "1.0", 6)
                 .addDep("ccc", createModuleKey("ccc", "3.0"))
                 .setRegistry(registry2)
                 .build());
@@ -469,7 +469,7 @@
     ModuleFileValue moduleFileValue = result.get(skyKey);
     assertThat(moduleFileValue.getModule())
         .isEqualTo(
-            ModuleBuilder.create("mymod", "1.0")
+            InterimModuleBuilder.create("mymod", "1.0")
                 .addDep("rules_jvm_external", createModuleKey("rules_jvm_external", "2.0"))
                 .setRegistry(registry)
                 .addExtensionUsage(
@@ -590,7 +590,7 @@
     ModuleFileValue moduleFileValue = result.get(skyKey);
     assertThat(moduleFileValue.getModule())
         .isEqualTo(
-            ModuleBuilder.create("", "")
+            InterimModuleBuilder.create("", "")
                 .setKey(ModuleKey.ROOT)
                 .addExtensionUsage(
                     ModuleExtensionUsage.builder()
@@ -687,7 +687,7 @@
     ModuleFileValue moduleFileValue = result.get(skyKey);
     assertThat(moduleFileValue.getModule())
         .isEqualTo(
-            ModuleBuilder.create("mymod", "1.0")
+            InterimModuleBuilder.create("mymod", "1.0")
                 .setRegistry(registry)
                 .addExtensionUsage(
                     ModuleExtensionUsage.builder()
@@ -901,7 +901,7 @@
     RootModuleFileValue moduleFileValue = result.get(skyKey);
     assertThat(moduleFileValue.getModule())
         .isEqualTo(
-            ModuleBuilder.create("", "")
+            InterimModuleBuilder.create("", "")
                 .addDep("bazel_tools", createModuleKey("bazel_tools", ""))
                 .addDep("local_config_platform", createModuleKey("local_config_platform", ""))
                 .addDep("foo", createModuleKey("foo", "1.0"))
@@ -938,7 +938,7 @@
     ModuleFileValue moduleFileValue = result.get(skyKey);
     assertThat(moduleFileValue.getModule())
         .isEqualTo(
-            ModuleBuilder.create("bazel_tools", "1.0")
+            InterimModuleBuilder.create("bazel_tools", "1.0")
                 .setKey(createModuleKey("bazel_tools", ""))
                 .addDep("local_config_platform", createModuleKey("local_config_platform", ""))
                 .addDep("foo", createModuleKey("foo", "2.0"))
@@ -962,7 +962,10 @@
     RootModuleFileValue rootModuleFileValue = result.get(ModuleFileValue.KEY_FOR_ROOT_MODULE);
     assertThat(rootModuleFileValue.getModule())
         .isEqualTo(
-            ModuleBuilder.create("aaa", "0.1").setKey(ModuleKey.ROOT).setRepoName("bbb").build());
+            InterimModuleBuilder.create("aaa", "0.1")
+                .setKey(ModuleKey.ROOT)
+                .setRepoName("bbb")
+                .build());
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleTest.java
index 2428755..d0a5b62 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleTest.java
@@ -15,10 +15,10 @@
 package com.google.devtools.build.lib.bazel.bzlmod;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.buildModule;
 import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createModuleKey;
 import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createRepositoryMapping;
 
-import com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.ModuleBuilder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -28,30 +28,10 @@
 public class ModuleTest {
 
   @Test
-  public void withDepKeysTransformed() throws Exception {
-    assertThat(
-            ModuleBuilder.create("", "")
-                .addDep("dep_foo", createModuleKey("foo", "1.0"))
-                .addDep("dep_bar", createModuleKey("bar", "2.0"))
-                .build()
-                .withDepKeysTransformed(
-                    key ->
-                        createModuleKey(
-                            key.getName() + "_new", key.getVersion().getOriginal() + ".1")))
-        .isEqualTo(
-            ModuleBuilder.create("", "")
-                .addDep("dep_foo", createModuleKey("foo_new", "1.0.1"))
-                .addOriginalDep("dep_foo", createModuleKey("foo", "1.0"))
-                .addDep("dep_bar", createModuleKey("bar_new", "2.0.1"))
-                .addOriginalDep("dep_bar", createModuleKey("bar", "2.0"))
-                .build());
-  }
-
-  @Test
   public void getRepoMapping() throws Exception {
     ModuleKey key = createModuleKey("test_module", "1.0");
     Module module =
-        ModuleBuilder.create(key.getName(), key.getVersion())
+        buildModule("test_module", "1.0")
             .addDep("my_foo", createModuleKey("foo", "1.0"))
             .addDep("my_bar", createModuleKey("bar", "2.0"))
             .addDep("my_root", ModuleKey.ROOT)
@@ -73,7 +53,7 @@
   @Test
   public void getRepoMapping_asMainModule() throws Exception {
     Module module =
-        ModuleBuilder.create("test_module", "1.0")
+        buildModule("test_module", "1.0")
             .setKey(ModuleKey.ROOT)
             .addDep("my_foo", createModuleKey("foo", "1.0"))
             .addDep("my_bar", createModuleKey("bar", "2.0"))
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/SelectionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/SelectionTest.java
index 2aed346..5cc7c3b 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/SelectionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/SelectionTest.java
@@ -16,12 +16,13 @@
 package com.google.devtools.build.lib.bazel.bzlmod;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createDepSpec;
 import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createModuleKey;
 import static org.junit.Assert.assertThrows;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.ModuleBuilder;
+import com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.InterimModuleBuilder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -32,167 +33,282 @@
 
   @Test
   public void diamond_simple() throws Exception {
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
                     .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0")
+                InterimModuleBuilder.create("bbb", "1.0")
                     .addDep("ddd_from_bbb", createModuleKey("ddd", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("ccc", "2.0")
+                InterimModuleBuilder.create("ccc", "2.0")
                     .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ddd", "1.0", 1).buildEntry())
-            .put(ModuleBuilder.create("ddd", "2.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "2.0", 1).buildEntry())
             .buildOrThrow();
 
-    BazelModuleResolutionValue selectionResult =
-        Selection.run(depGraph, /* overrides= */ ImmutableMap.of());
+    Selection.Result selectionResult = Selection.run(depGraph, /* overrides= */ ImmutableMap.of());
     assertThat(selectionResult.getResolvedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", Version.EMPTY)
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
                 .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .addDep("ddd_from_bbb", createModuleKey("ddd", "2.0"))
                 .addOriginalDep("ddd_from_bbb", createModuleKey("ddd", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "2.0")
+            InterimModuleBuilder.create("ccc", "2.0")
                 .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ddd", "2.0", 1).buildEntry())
+            InterimModuleBuilder.create("ddd", "2.0", 1).buildEntry())
         .inOrder();
 
     assertThat(selectionResult.getUnprunedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", Version.EMPTY)
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
                 .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .addDep("ddd_from_bbb", createModuleKey("ddd", "2.0"))
                 .addOriginalDep("ddd_from_bbb", createModuleKey("ddd", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "2.0")
+            InterimModuleBuilder.create("ccc", "2.0")
                 .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ddd", "1.0", 1).buildEntry(),
-            ModuleBuilder.create("ddd", "2.0", 1).buildEntry());
+            InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry(),
+            InterimModuleBuilder.create("ddd", "2.0", 1).buildEntry());
+  }
+
+  @Test
+  public void diamond_withIgnoredNonAffectingMaxCompatibilityLevel() throws Exception {
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
+            .put(
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
+                    .setKey(ModuleKey.ROOT)
+                    .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                    .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                    .buildEntry())
+            .put(
+                InterimModuleBuilder.create("bbb", "1.0")
+                    .addDep("ddd_from_bbb", createDepSpec("ddd", "1.0", 3))
+                    .buildEntry())
+            .put(
+                InterimModuleBuilder.create("ccc", "2.0")
+                    .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
+                    .buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "2.0", 1).buildEntry())
+            .buildOrThrow();
+
+    Selection.Result selectionResult = Selection.run(depGraph, /* overrides= */ ImmutableMap.of());
+    assertThat(selectionResult.getResolvedDepGraph().entrySet())
+        .containsExactly(
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
+                .setKey(ModuleKey.ROOT)
+                .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("bbb", "1.0")
+                .addDep("ddd_from_bbb", createModuleKey("ddd", "2.0"))
+                .addOriginalDep("ddd_from_bbb", createDepSpec("ddd", "1.0", 3))
+                .buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0")
+                .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("ddd", "2.0", 1).buildEntry())
+        .inOrder();
+
+    assertThat(selectionResult.getUnprunedDepGraph().entrySet())
+        .containsExactly(
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
+                .setKey(ModuleKey.ROOT)
+                .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("bbb", "1.0")
+                .addDep("ddd_from_bbb", createModuleKey("ddd", "2.0"))
+                .addOriginalDep("ddd_from_bbb", createDepSpec("ddd", "1.0", 3))
+                .buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0")
+                .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry(),
+            InterimModuleBuilder.create("ddd", "2.0", 1).buildEntry());
+  }
+
+  @Test
+  public void diamond_withSelectedNonAffectingMaxCompatibilityLevel() throws Exception {
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
+            .put(
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
+                    .setKey(ModuleKey.ROOT)
+                    .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                    .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                    .buildEntry())
+            .put(
+                InterimModuleBuilder.create("bbb", "1.0")
+                    .addDep("ddd_from_bbb", createModuleKey("ddd", "1.0"))
+                    .buildEntry())
+            .put(
+                InterimModuleBuilder.create("ccc", "2.0")
+                    .addDep("ddd_from_ccc", createDepSpec("ddd", "2.0", 4))
+                    .buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "2.0", 1).buildEntry())
+            .buildOrThrow();
+
+    Selection.Result selectionResult = Selection.run(depGraph, /* overrides= */ ImmutableMap.of());
+    assertThat(selectionResult.getResolvedDepGraph().entrySet())
+        .containsExactly(
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
+                .setKey(ModuleKey.ROOT)
+                .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("bbb", "1.0")
+                .addDep("ddd_from_bbb", createModuleKey("ddd", "2.0"))
+                .addOriginalDep("ddd_from_bbb", createModuleKey("ddd", "1.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0")
+                .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
+                .addOriginalDep("ddd_from_ccc", createDepSpec("ddd", "2.0", 4))
+                .buildEntry(),
+            InterimModuleBuilder.create("ddd", "2.0", 1).buildEntry())
+        .inOrder();
+
+    assertThat(selectionResult.getUnprunedDepGraph().entrySet())
+        .containsExactly(
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
+                .setKey(ModuleKey.ROOT)
+                .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("bbb", "1.0")
+                .addDep("ddd_from_bbb", createModuleKey("ddd", "2.0"))
+                .addOriginalDep("ddd_from_bbb", createModuleKey("ddd", "1.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0")
+                .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
+                .addOriginalDep("ddd_from_ccc", createDepSpec("ddd", "2.0", 4))
+                .buildEntry(),
+            InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry(),
+            InterimModuleBuilder.create("ddd", "2.0", 1).buildEntry());
   }
 
   @Test
   public void diamond_withFurtherRemoval() throws Exception {
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb", createModuleKey("bbb", "1.0"))
                     .addDep("ccc", createModuleKey("ccc", "2.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0")
+                InterimModuleBuilder.create("bbb", "1.0")
                     .addDep("ddd", createModuleKey("ddd", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("ccc", "2.0")
+                InterimModuleBuilder.create("ccc", "2.0")
                     .addDep("ddd", createModuleKey("ddd", "2.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("ddd", "1.0")
+                InterimModuleBuilder.create("ddd", "1.0")
                     .addDep("eee", createModuleKey("eee", "1.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ddd", "2.0").buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "2.0").buildEntry())
             // Only D@1.0 needs E. When D@1.0 is removed, E should be gone as well (even though
             // E@1.0 is selected for E).
-            .put(ModuleBuilder.create("eee", "1.0").buildEntry())
+            .put(InterimModuleBuilder.create("eee", "1.0").buildEntry())
             .build();
 
-    BazelModuleResolutionValue selectionResult =
-        Selection.run(depGraph, /* overrides= */ ImmutableMap.of());
+    Selection.Result selectionResult = Selection.run(depGraph, /* overrides= */ ImmutableMap.of());
     assertThat(selectionResult.getResolvedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", Version.EMPTY)
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "1.0"))
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .addDep("ddd", createModuleKey("ddd", "2.0"))
                 .addOriginalDep("ddd", createModuleKey("ddd", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "2.0")
+            InterimModuleBuilder.create("ccc", "2.0")
                 .addDep("ddd", createModuleKey("ddd", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ddd", "2.0").buildEntry())
+            InterimModuleBuilder.create("ddd", "2.0").buildEntry())
         .inOrder();
 
     assertThat(selectionResult.getUnprunedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", Version.EMPTY)
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "1.0"))
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .addDep("ddd", createModuleKey("ddd", "2.0"))
                 .addOriginalDep("ddd", createModuleKey("ddd", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "2.0")
+            InterimModuleBuilder.create("ccc", "2.0")
                 .addDep("ddd", createModuleKey("ddd", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ddd", "2.0").buildEntry(),
-            ModuleBuilder.create("ddd", "1.0")
+            InterimModuleBuilder.create("ddd", "2.0").buildEntry(),
+            InterimModuleBuilder.create("ddd", "1.0")
                 .addDep("eee", createModuleKey("eee", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("eee", "1.0").buildEntry());
+            InterimModuleBuilder.create("eee", "1.0").buildEntry());
   }
 
   @Test
   public void circularDependencyDueToSelection() throws Exception {
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb", createModuleKey("bbb", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0")
+                InterimModuleBuilder.create("bbb", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "2.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("ccc", "2.0")
+                InterimModuleBuilder.create("ccc", "2.0")
                     .addDep("bbb", createModuleKey("bbb", "1.0-pre"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0-pre")
+                InterimModuleBuilder.create("bbb", "1.0-pre")
                     .addDep("ddd", createModuleKey("ddd", "1.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ddd", "1.0").buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "1.0").buildEntry())
             .buildOrThrow();
 
-    BazelModuleResolutionValue selectionResult =
-        Selection.run(depGraph, /* overrides= */ ImmutableMap.of());
+    Selection.Result selectionResult = Selection.run(depGraph, /* overrides= */ ImmutableMap.of());
     assertThat(selectionResult.getResolvedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", Version.EMPTY)
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "2.0")
+            InterimModuleBuilder.create("ccc", "2.0")
                 .addDep("bbb", createModuleKey("bbb", "1.0"))
                 .addOriginalDep("bbb", createModuleKey("bbb", "1.0-pre"))
                 .buildEntry())
@@ -201,49 +317,49 @@
 
     assertThat(selectionResult.getUnprunedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", Version.EMPTY)
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "2.0")
+            InterimModuleBuilder.create("ccc", "2.0")
                 .addDep("bbb", createModuleKey("bbb", "1.0"))
                 .addOriginalDep("bbb", createModuleKey("bbb", "1.0-pre"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0-pre")
+            InterimModuleBuilder.create("bbb", "1.0-pre")
                 .addDep("ddd", createModuleKey("ddd", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ddd", "1.0").buildEntry());
+            InterimModuleBuilder.create("ddd", "1.0").buildEntry());
   }
 
   @Test
   public void differentCompatibilityLevelIsRejected() throws Exception {
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
                     .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0")
+                InterimModuleBuilder.create("bbb", "1.0")
                     .addDep("ddd_from_bbb", createModuleKey("ddd", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("ccc", "2.0")
+                InterimModuleBuilder.create("ccc", "2.0")
                     .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ddd", "1.0", 1).buildEntry())
-            .put(ModuleBuilder.create("ddd", "2.0", 2).buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "2.0", 2).buildEntry())
             .buildOrThrow();
 
     ExternalDepsException e =
         assertThrows(
             ExternalDepsException.class,
-            () -> Selection.run(depGraph, /*overrides=*/ ImmutableMap.of()));
+            () -> Selection.run(depGraph, /* overrides= */ ImmutableMap.of()));
     String error = e.getMessage();
     assertThat(error).contains("bbb@1.0 depends on ddd@1.0 with compatibility level 1");
     assertThat(error).contains("ccc@2.0 depends on ddd@2.0 with compatibility level 2");
@@ -251,15 +367,281 @@
   }
 
   @Test
+  public void differentCompatibilityLevelWithMaxCompatibilityLevelIsRejected() throws Exception {
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
+            .put(
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
+                    .setKey(ModuleKey.ROOT)
+                    .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                    .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                    .buildEntry())
+            .put(
+                InterimModuleBuilder.create("bbb", "1.0")
+                    .addDep("ddd_from_bbb", createModuleKey("ddd", "1.0"))
+                    .buildEntry())
+            .put(
+                InterimModuleBuilder.create("ccc", "2.0")
+                    .addDep("ddd_from_ccc", createDepSpec("ddd", "2.0", 3))
+                    .buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "2.0", 2).buildEntry())
+            .buildOrThrow();
+
+    ExternalDepsException e =
+        assertThrows(
+            ExternalDepsException.class,
+            () -> Selection.run(depGraph, /* overrides= */ ImmutableMap.of()));
+    String error = e.getMessage();
+    assertThat(error).contains("bbb@1.0 depends on ddd@1.0 with compatibility level 1");
+    assertThat(error).contains("ccc@2.0 depends on ddd@2.0 with compatibility level 2");
+    assertThat(error).contains("which is different");
+  }
+
+  @Test
+  public void maxCompatibilityBasedSelection() throws Exception {
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
+            .put(
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
+                    .setKey(ModuleKey.ROOT)
+                    .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                    .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                    .buildEntry())
+            .put(
+                InterimModuleBuilder.create("bbb", "1.0")
+                    .addDep("ddd_from_bbb", createDepSpec("ddd", "1.0", 2))
+                    .buildEntry())
+            .put(
+                InterimModuleBuilder.create("ccc", "2.0")
+                    .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
+                    .buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "2.0", 2).buildEntry())
+            .buildOrThrow();
+
+    Selection.Result selectionResult = Selection.run(depGraph, /* overrides= */ ImmutableMap.of());
+    assertThat(selectionResult.getResolvedDepGraph().entrySet())
+        .containsExactly(
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
+                .setKey(ModuleKey.ROOT)
+                .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("bbb", "1.0")
+                .addDep("ddd_from_bbb", createModuleKey("ddd", "2.0"))
+                .addOriginalDep("ddd_from_bbb", createDepSpec("ddd", "1.0", 2))
+                .buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0")
+                .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("ddd", "2.0", 2).buildEntry())
+        .inOrder();
+
+    assertThat(selectionResult.getUnprunedDepGraph().entrySet())
+        .containsExactly(
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
+                .setKey(ModuleKey.ROOT)
+                .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("bbb", "1.0")
+                .addDep("ddd_from_bbb", createModuleKey("ddd", "2.0"))
+                .addOriginalDep("ddd_from_bbb", createDepSpec("ddd", "1.0", 2))
+                .buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0")
+                .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry(),
+            InterimModuleBuilder.create("ddd", "2.0", 2).buildEntry());
+  }
+
+  @Test
+  public void maxCompatibilityBasedSelection_sameVersion() throws Exception {
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
+            .put(
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
+                    .setKey(ModuleKey.ROOT)
+                    .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                    .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                    .buildEntry())
+            .put(
+                InterimModuleBuilder.create("bbb", "1.0")
+                    .addDep("ddd_from_bbb", createDepSpec("ddd", "2.0", 3))
+                    .buildEntry())
+            .put(
+                InterimModuleBuilder.create("ccc", "2.0")
+                    .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
+                    .buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "2.0", 2).buildEntry())
+            .buildOrThrow();
+
+    Selection.Result selectionResult = Selection.run(depGraph, /* overrides= */ ImmutableMap.of());
+    assertThat(selectionResult.getResolvedDepGraph().entrySet())
+        .containsExactly(
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
+                .setKey(ModuleKey.ROOT)
+                .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("bbb", "1.0")
+                .addDep("ddd_from_bbb", createModuleKey("ddd", "2.0"))
+                .addOriginalDep("ddd_from_bbb", createDepSpec("ddd", "2.0", 3))
+                .buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0")
+                .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("ddd", "2.0", 2).buildEntry())
+        .inOrder();
+
+    assertThat(selectionResult.getUnprunedDepGraph().entrySet())
+        .containsExactly(
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
+                .setKey(ModuleKey.ROOT)
+                .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("bbb", "1.0")
+                .addDep("ddd_from_bbb", createModuleKey("ddd", "2.0"))
+                .addOriginalDep("ddd_from_bbb", createDepSpec("ddd", "2.0", 3))
+                .buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0")
+                .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("ddd", "2.0", 2).buildEntry());
+  }
+
+  @Test
+  public void maxCompatibilityBasedSelection_limitedToMax() throws Exception {
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
+            .put(
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
+                    .setKey(ModuleKey.ROOT)
+                    .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
+                    .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
+                    .buildEntry())
+            .put(
+                InterimModuleBuilder.create("bbb", "1.0")
+                    .addDep("ddd_from_bbb", createDepSpec("ddd", "1.0", 2))
+                    .buildEntry())
+            .put(
+                InterimModuleBuilder.create("ccc", "2.0")
+                    .addDep("ddd_from_ccc", createModuleKey("ddd", "3.0"))
+                    .buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "3.0", 3).buildEntry())
+            .buildOrThrow();
+
+    ExternalDepsException e =
+        assertThrows(
+            ExternalDepsException.class,
+            () -> Selection.run(depGraph, /* overrides= */ ImmutableMap.of()));
+    String error = e.getMessage();
+    assertThat(error).contains("bbb@1.0 depends on ddd@1.0 with compatibility level 1");
+    assertThat(error).contains("ccc@2.0 depends on ddd@3.0 with compatibility level 3");
+    assertThat(error).contains("which is different");
+  }
+
+  @Test
+  public void maxCompatibilityBasedSelection_unreferencedNotSelected() throws Exception {
+    // aaa 1.0 -> bbb 1.0 -> ccc 2.0
+    //       \-> ccc 1.0 (max_compatibility_level=2)
+    //        \-> ddd 1.0 -> bbb 1.1
+    //         \-> eee 1.0 -> ccc 1.1
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
+            .put(
+                InterimModuleBuilder.create("aaa", "1.0")
+                    .setKey(ModuleKey.ROOT)
+                    .addDep("bbb", createModuleKey("bbb", "1.0"))
+                    .addDep("ccc", createDepSpec("ccc", "1.0", 2))
+                    .addDep("ddd", createModuleKey("ddd", "1.0"))
+                    .addDep("eee", createModuleKey("eee", "1.0"))
+                    .buildEntry())
+            .put(
+                InterimModuleBuilder.create("bbb", "1.0")
+                    .addDep("ccc", createModuleKey("ccc", "2.0"))
+                    .buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "2.0", 2).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.0", 1).buildEntry())
+            .put(
+                InterimModuleBuilder.create("ddd", "1.0")
+                    .addDep("bbb", createModuleKey("bbb", "1.1"))
+                    .buildEntry())
+            .put(InterimModuleBuilder.create("bbb", "1.1").buildEntry())
+            .put(
+                InterimModuleBuilder.create("eee", "1.0")
+                    .addDep("ccc", createModuleKey("ccc", "1.1"))
+                    .buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.1", 1).buildEntry())
+            .buildOrThrow();
+
+    // After selection, ccc 2.0 is gone, so ccc 1.0 (max_compatibility_level=2) doesn't upgrade to
+    // ccc 2.0, and only upgrades to ccc 1.1
+    // aaa 1.0 -> bbb 1.1
+    //       \-> ccc 1.1
+    //        \-> ddd 1.0 -> bbb 1.1
+    //         \-> eee 1.0 -> ccc 1.1
+    Selection.Result selectionResult = Selection.run(depGraph, /* overrides= */ ImmutableMap.of());
+    assertThat(selectionResult.getResolvedDepGraph().entrySet())
+        .containsExactly(
+            InterimModuleBuilder.create("aaa", "1.0")
+                .setKey(ModuleKey.ROOT)
+                .addDep("bbb", createModuleKey("bbb", "1.1"))
+                .addOriginalDep("bbb", createModuleKey("bbb", "1.0"))
+                .addDep("ccc", createModuleKey("ccc", "1.1"))
+                .addOriginalDep("ccc", createDepSpec("ccc", "1.0", 2))
+                .addDep("ddd", createModuleKey("ddd", "1.0"))
+                .addDep("eee", createModuleKey("eee", "1.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("bbb", "1.1").buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.1", 1).buildEntry(),
+            InterimModuleBuilder.create("ddd", "1.0")
+                .addDep("bbb", createModuleKey("bbb", "1.1"))
+                .buildEntry(),
+            InterimModuleBuilder.create("eee", "1.0")
+                .addDep("ccc", createModuleKey("ccc", "1.1"))
+                .buildEntry())
+        .inOrder();
+
+    assertThat(selectionResult.getUnprunedDepGraph().entrySet())
+        .containsExactly(
+            InterimModuleBuilder.create("aaa", "1.0")
+                .setKey(ModuleKey.ROOT)
+                .addDep("bbb", createModuleKey("bbb", "1.1"))
+                .addOriginalDep("bbb", createModuleKey("bbb", "1.0"))
+                .addDep("ccc", createModuleKey("ccc", "1.1"))
+                .addOriginalDep("ccc", createDepSpec("ccc", "1.0", 2))
+                .addDep("ddd", createModuleKey("ddd", "1.0"))
+                .addDep("eee", createModuleKey("eee", "1.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("bbb", "1.0")
+                .addDep("ccc", createModuleKey("ccc", "2.0"))
+                .buildEntry(),
+            InterimModuleBuilder.create("bbb", "1.1").buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.0", 1).buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.1", 1).buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0", 2).buildEntry(),
+            InterimModuleBuilder.create("ddd", "1.0")
+                .addDep("bbb", createModuleKey("bbb", "1.1"))
+                .buildEntry(),
+            InterimModuleBuilder.create("eee", "1.0")
+                .addDep("ccc", createModuleKey("ccc", "1.1"))
+                .buildEntry());
+  }
+
+  @Test
   public void differentCompatibilityLevelIsOkIfUnreferenced() throws Exception {
     // aaa 1.0 -> bbb 1.0 -> ccc 2.0
     //       \-> ccc 1.0
     //        \-> ddd 1.0 -> bbb 1.1
     //         \-> eee 1.0 -> ccc 1.1
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", "1.0")
+                InterimModuleBuilder.create("aaa", "1.0")
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb", createModuleKey("bbb", "1.0"))
                     .addDep("ccc", createModuleKey("ccc", "1.0"))
@@ -267,21 +649,21 @@
                     .addDep("eee", createModuleKey("eee", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0")
+                InterimModuleBuilder.create("bbb", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "2.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ccc", "2.0", 2).buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "2.0", 2).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.0", 1).buildEntry())
             .put(
-                ModuleBuilder.create("ddd", "1.0")
+                InterimModuleBuilder.create("ddd", "1.0")
                     .addDep("bbb", createModuleKey("bbb", "1.1"))
                     .buildEntry())
-            .put(ModuleBuilder.create("bbb", "1.1").buildEntry())
+            .put(InterimModuleBuilder.create("bbb", "1.1").buildEntry())
             .put(
-                ModuleBuilder.create("eee", "1.0")
+                InterimModuleBuilder.create("eee", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "1.1"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.1", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.1", 1).buildEntry())
             .buildOrThrow();
 
     // After selection, ccc 2.0 is gone, so we're okay.
@@ -289,11 +671,10 @@
     //       \-> ccc 1.1
     //        \-> ddd 1.0 -> bbb 1.1
     //         \-> eee 1.0 -> ccc 1.1
-    BazelModuleResolutionValue selectionResult =
-        Selection.run(depGraph, /* overrides= */ ImmutableMap.of());
+    Selection.Result selectionResult = Selection.run(depGraph, /* overrides= */ ImmutableMap.of());
     assertThat(selectionResult.getResolvedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", "1.0")
+            InterimModuleBuilder.create("aaa", "1.0")
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "1.1"))
                 .addOriginalDep("bbb", createModuleKey("bbb", "1.0"))
@@ -302,19 +683,19 @@
                 .addDep("ddd", createModuleKey("ddd", "1.0"))
                 .addDep("eee", createModuleKey("eee", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.1").buildEntry(),
-            ModuleBuilder.create("ccc", "1.1", 1).buildEntry(),
-            ModuleBuilder.create("ddd", "1.0")
+            InterimModuleBuilder.create("bbb", "1.1").buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.1", 1).buildEntry(),
+            InterimModuleBuilder.create("ddd", "1.0")
                 .addDep("bbb", createModuleKey("bbb", "1.1"))
                 .buildEntry(),
-            ModuleBuilder.create("eee", "1.0")
+            InterimModuleBuilder.create("eee", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "1.1"))
                 .buildEntry())
         .inOrder();
 
     assertThat(selectionResult.getUnprunedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", "1.0")
+            InterimModuleBuilder.create("aaa", "1.0")
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb", createModuleKey("bbb", "1.1"))
                 .addOriginalDep("bbb", createModuleKey("bbb", "1.0"))
@@ -323,33 +704,33 @@
                 .addDep("ddd", createModuleKey("ddd", "1.0"))
                 .addDep("eee", createModuleKey("eee", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.1").buildEntry(),
-            ModuleBuilder.create("ccc", "1.0", 1).buildEntry(),
-            ModuleBuilder.create("ccc", "1.1", 1).buildEntry(),
-            ModuleBuilder.create("ccc", "2.0", 2).buildEntry(),
-            ModuleBuilder.create("ddd", "1.0")
+            InterimModuleBuilder.create("bbb", "1.1").buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.0", 1).buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.1", 1).buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0", 2).buildEntry(),
+            InterimModuleBuilder.create("ddd", "1.0")
                 .addDep("bbb", createModuleKey("bbb", "1.1"))
                 .buildEntry(),
-            ModuleBuilder.create("eee", "1.0")
+            InterimModuleBuilder.create("eee", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "1.1"))
                 .buildEntry());
   }
 
   @Test
   public void multipleVersionOverride_fork_allowedVersionMissingInDepGraph() throws Exception {
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb1", createModuleKey("bbb", "1.0"))
                     .addDep("bbb2", createModuleKey("bbb", "2.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("bbb", "1.0").buildEntry())
-            .put(ModuleBuilder.create("bbb", "2.0").buildEntry())
+            .put(InterimModuleBuilder.create("bbb", "1.0").buildEntry())
+            .put(InterimModuleBuilder.create("bbb", "2.0").buildEntry())
             .buildOrThrow();
     ImmutableMap<String, ModuleOverride> overrides =
         ImmutableMap.of(
@@ -370,16 +751,16 @@
   @Test
   public void multipleVersionOverride_fork_goodCase() throws Exception {
     // For more complex good cases, see the "diamond" test cases below.
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb1", createModuleKey("bbb", "1.0"))
                     .addDep("bbb2", createModuleKey("bbb", "2.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("bbb", "1.0").buildEntry())
-            .put(ModuleBuilder.create("bbb", "2.0").buildEntry())
+            .put(InterimModuleBuilder.create("bbb", "1.0").buildEntry())
+            .put(InterimModuleBuilder.create("bbb", "2.0").buildEntry())
             .buildOrThrow();
     ImmutableMap<String, ModuleOverride> overrides =
         ImmutableMap.of(
@@ -387,16 +768,16 @@
             MultipleVersionOverride.create(
                 ImmutableList.of(Version.parse("1.0"), Version.parse("2.0")), ""));
 
-    BazelModuleResolutionValue selectionResult = Selection.run(depGraph, overrides);
+    Selection.Result selectionResult = Selection.run(depGraph, overrides);
     assertThat(selectionResult.getResolvedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", Version.EMPTY)
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb1", createModuleKey("bbb", "1.0"))
                 .addDep("bbb2", createModuleKey("bbb", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0").buildEntry(),
-            ModuleBuilder.create("bbb", "2.0").buildEntry())
+            InterimModuleBuilder.create("bbb", "1.0").buildEntry(),
+            InterimModuleBuilder.create("bbb", "2.0").buildEntry())
         .inOrder();
 
     assertThat(selectionResult.getUnprunedDepGraph())
@@ -405,18 +786,18 @@
 
   @Test
   public void multipleVersionOverride_fork_sameVersionUsedTwice() throws Exception {
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb1", createModuleKey("bbb", "1.0"))
                     .addDep("bbb2", createModuleKey("bbb", "1.3"))
                     .addDep("bbb3", createModuleKey("bbb", "1.5"))
                     .buildEntry())
-            .put(ModuleBuilder.create("bbb", "1.0").buildEntry())
-            .put(ModuleBuilder.create("bbb", "1.3").buildEntry())
-            .put(ModuleBuilder.create("bbb", "1.5").buildEntry())
+            .put(InterimModuleBuilder.create("bbb", "1.0").buildEntry())
+            .put(InterimModuleBuilder.create("bbb", "1.3").buildEntry())
+            .put(InterimModuleBuilder.create("bbb", "1.5").buildEntry())
             .buildOrThrow();
     ImmutableMap<String, ModuleOverride> overrides =
         ImmutableMap.of(
@@ -435,24 +816,24 @@
 
   @Test
   public void multipleVersionOverride_diamond_differentCompatibilityLevels() throws Exception {
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
                     .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0")
+                InterimModuleBuilder.create("bbb", "1.0")
                     .addDep("ddd_from_bbb", createModuleKey("ddd", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("ccc", "2.0")
+                InterimModuleBuilder.create("ccc", "2.0")
                     .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ddd", "1.0", 1).buildEntry())
-            .put(ModuleBuilder.create("ddd", "2.0", 2).buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "2.0", 2).buildEntry())
             .buildOrThrow();
     ImmutableMap<String, ModuleOverride> overrides =
         ImmutableMap.of(
@@ -460,22 +841,22 @@
             MultipleVersionOverride.create(
                 ImmutableList.of(Version.parse("1.0"), Version.parse("2.0")), ""));
 
-    BazelModuleResolutionValue selectionResult = Selection.run(depGraph, overrides);
+    Selection.Result selectionResult = Selection.run(depGraph, overrides);
     assertThat(selectionResult.getResolvedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", Version.EMPTY)
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
                 .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .addDep("ddd_from_bbb", createModuleKey("ddd", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "2.0")
+            InterimModuleBuilder.create("ccc", "2.0")
                 .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ddd", "1.0", 1).buildEntry(),
-            ModuleBuilder.create("ddd", "2.0", 2).buildEntry())
+            InterimModuleBuilder.create("ddd", "1.0", 1).buildEntry(),
+            InterimModuleBuilder.create("ddd", "2.0", 2).buildEntry())
         .inOrder();
 
     assertThat(selectionResult.getUnprunedDepGraph())
@@ -484,24 +865,24 @@
 
   @Test
   public void multipleVersionOverride_diamond_sameCompatibilityLevel() throws Exception {
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
                     .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb", "1.0")
+                InterimModuleBuilder.create("bbb", "1.0")
                     .addDep("ddd_from_bbb", createModuleKey("ddd", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("ccc", "2.0")
+                InterimModuleBuilder.create("ccc", "2.0")
                     .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ddd", "1.0").buildEntry())
-            .put(ModuleBuilder.create("ddd", "2.0").buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "1.0").buildEntry())
+            .put(InterimModuleBuilder.create("ddd", "2.0").buildEntry())
             .buildOrThrow();
     ImmutableMap<String, ModuleOverride> overrides =
         ImmutableMap.of(
@@ -509,22 +890,22 @@
             MultipleVersionOverride.create(
                 ImmutableList.of(Version.parse("1.0"), Version.parse("2.0")), ""));
 
-    BazelModuleResolutionValue selectionResult = Selection.run(depGraph, overrides);
+    Selection.Result selectionResult = Selection.run(depGraph, overrides);
     assertThat(selectionResult.getResolvedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", Version.EMPTY)
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb_from_aaa", createModuleKey("bbb", "1.0"))
                 .addDep("ccc_from_aaa", createModuleKey("ccc", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb", "1.0")
+            InterimModuleBuilder.create("bbb", "1.0")
                 .addDep("ddd_from_bbb", createModuleKey("ddd", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "2.0")
+            InterimModuleBuilder.create("ccc", "2.0")
                 .addDep("ddd_from_ccc", createModuleKey("ddd", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ddd", "1.0").buildEntry(),
-            ModuleBuilder.create("ddd", "2.0").buildEntry())
+            InterimModuleBuilder.create("ddd", "1.0").buildEntry(),
+            InterimModuleBuilder.create("ddd", "2.0").buildEntry())
         .inOrder();
 
     assertThat(selectionResult.getUnprunedDepGraph())
@@ -538,10 +919,10 @@
     //     \-> bbb3@1.0 -> ccc@1.5
     //     \-> bbb4@1.0 -> ccc@1.7  [allowed]
     //     \-> bbb5@1.0 -> ccc@2.0  [allowed]
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb1", createModuleKey("bbb1", "1.0"))
                     .addDep("bbb2", createModuleKey("bbb2", "1.0"))
@@ -550,30 +931,30 @@
                     .addDep("bbb5", createModuleKey("bbb5", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb1", "1.0")
+                InterimModuleBuilder.create("bbb1", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb2", "1.0")
+                InterimModuleBuilder.create("bbb2", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "1.3"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb3", "1.0")
+                InterimModuleBuilder.create("bbb3", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "1.5"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb4", "1.0")
+                InterimModuleBuilder.create("bbb4", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "1.7"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb5", "1.0")
+                InterimModuleBuilder.create("bbb5", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "2.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.0", 1).buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.3", 1).buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.5", 1).buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.7", 1).buildEntry())
-            .put(ModuleBuilder.create("ccc", "2.0", 2).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.3", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.5", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.7", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "2.0", 2).buildEntry())
             .buildOrThrow();
     ImmutableMap<String, ModuleOverride> overrides =
         ImmutableMap.of(
@@ -587,10 +968,10 @@
     //     \-> bbb3@1.0 -> ccc@1.7  [originally ccc@1.5]
     //     \-> bbb4@1.0 -> ccc@1.7  [allowed]
     //     \-> bbb5@1.0 -> ccc@2.0  [allowed]
-    BazelModuleResolutionValue selectionResult = Selection.run(depGraph, overrides);
+    Selection.Result selectionResult = Selection.run(depGraph, overrides);
     assertThat(selectionResult.getResolvedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", Version.EMPTY)
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb1", createModuleKey("bbb1", "1.0"))
                 .addDep("bbb2", createModuleKey("bbb2", "1.0"))
@@ -598,31 +979,31 @@
                 .addDep("bbb4", createModuleKey("bbb4", "1.0"))
                 .addDep("bbb5", createModuleKey("bbb5", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb1", "1.0")
+            InterimModuleBuilder.create("bbb1", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "1.3"))
                 .addOriginalDep("ccc", createModuleKey("ccc", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb2", "1.0")
+            InterimModuleBuilder.create("bbb2", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "1.3"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb3", "1.0")
+            InterimModuleBuilder.create("bbb3", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "1.7"))
                 .addOriginalDep("ccc", createModuleKey("ccc", "1.5"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb4", "1.0")
+            InterimModuleBuilder.create("bbb4", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "1.7"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb5", "1.0")
+            InterimModuleBuilder.create("bbb5", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "1.3", 1).buildEntry(),
-            ModuleBuilder.create("ccc", "1.7", 1).buildEntry(),
-            ModuleBuilder.create("ccc", "2.0", 2).buildEntry())
+            InterimModuleBuilder.create("ccc", "1.3", 1).buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.7", 1).buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0", 2).buildEntry())
         .inOrder();
 
     assertThat(selectionResult.getUnprunedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", Version.EMPTY)
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb1", createModuleKey("bbb1", "1.0"))
                 .addDep("bbb2", createModuleKey("bbb2", "1.0"))
@@ -630,28 +1011,28 @@
                 .addDep("bbb4", createModuleKey("bbb4", "1.0"))
                 .addDep("bbb5", createModuleKey("bbb5", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb1", "1.0")
+            InterimModuleBuilder.create("bbb1", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "1.3"))
                 .addOriginalDep("ccc", createModuleKey("ccc", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb2", "1.0")
+            InterimModuleBuilder.create("bbb2", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "1.3"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb3", "1.0")
+            InterimModuleBuilder.create("bbb3", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "1.7"))
                 .addOriginalDep("ccc", createModuleKey("ccc", "1.5"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb4", "1.0")
+            InterimModuleBuilder.create("bbb4", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "1.7"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb5", "1.0")
+            InterimModuleBuilder.create("bbb5", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .buildEntry(),
-            ModuleBuilder.create("ccc", "1.0", 1).buildEntry(),
-            ModuleBuilder.create("ccc", "1.3", 1).buildEntry(),
-            ModuleBuilder.create("ccc", "1.5", 1).buildEntry(),
-            ModuleBuilder.create("ccc", "1.7", 1).buildEntry(),
-            ModuleBuilder.create("ccc", "2.0", 2).buildEntry());
+            InterimModuleBuilder.create("ccc", "1.0", 1).buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.3", 1).buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.5", 1).buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.7", 1).buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0", 2).buildEntry());
   }
 
   @Test
@@ -659,30 +1040,30 @@
     // aaa --> bbb1@1.0 -> ccc@1.0  [allowed]
     //     \-> bbb2@1.0 -> ccc@1.7
     //     \-> bbb3@1.0 -> ccc@2.0  [allowed]
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb1", createModuleKey("bbb1", "1.0"))
                     .addDep("bbb2", createModuleKey("bbb2", "1.0"))
                     .addDep("bbb3", createModuleKey("bbb3", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb1", "1.0")
+                InterimModuleBuilder.create("bbb1", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb2", "1.0")
+                InterimModuleBuilder.create("bbb2", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "1.7"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb3", "1.0")
+                InterimModuleBuilder.create("bbb3", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "2.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.0", 1).buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.7", 1).buildEntry())
-            .put(ModuleBuilder.create("ccc", "2.0", 2).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.7", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "2.0", 2).buildEntry())
             .buildOrThrow();
     ImmutableMap<String, ModuleOverride> overrides =
         ImmutableMap.of(
@@ -704,30 +1085,30 @@
     // aaa --> bbb1@1.0 -> ccc@1.0  [allowed]
     //     \-> bbb2@1.0 -> ccc@2.0  [allowed]
     //     \-> bbb3@1.0 -> ccc@3.0
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb1", createModuleKey("bbb1", "1.0"))
                     .addDep("bbb2", createModuleKey("bbb2", "1.0"))
                     .addDep("bbb3", createModuleKey("bbb3", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb1", "1.0")
+                InterimModuleBuilder.create("bbb1", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb2", "1.0")
+                InterimModuleBuilder.create("bbb2", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "2.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb3", "1.0")
+                InterimModuleBuilder.create("bbb3", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "3.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.0", 1).buildEntry())
-            .put(ModuleBuilder.create("ccc", "2.0", 2).buildEntry())
-            .put(ModuleBuilder.create("ccc", "3.0", 3).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "2.0", 2).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "3.0", 3).buildEntry())
             .buildOrThrow();
     ImmutableMap<String, ModuleOverride> overrides =
         ImmutableMap.of(
@@ -752,10 +1133,10 @@
     //     \-> bbb3@1.0 --> ccc@2.0  [allowed]
     //     \            \-> bbb4@1.1
     //     \-> bbb4@1.0 --> ccc@3.0
-    ImmutableMap<ModuleKey, Module> depGraph =
-        ImmutableMap.<ModuleKey, Module>builder()
+    ImmutableMap<ModuleKey, InterimModule> depGraph =
+        ImmutableMap.<ModuleKey, InterimModule>builder()
             .put(
-                ModuleBuilder.create("aaa", Version.EMPTY)
+                InterimModuleBuilder.create("aaa", Version.EMPTY)
                     .setKey(ModuleKey.ROOT)
                     .addDep("bbb1", createModuleKey("bbb1", "1.0"))
                     .addDep("bbb2", createModuleKey("bbb2", "1.0"))
@@ -763,29 +1144,29 @@
                     .addDep("bbb4", createModuleKey("bbb4", "1.0"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb1", "1.0")
+                InterimModuleBuilder.create("bbb1", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "1.0"))
                     .addDep("bbb2", createModuleKey("bbb2", "1.1"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb2", "1.0")
+                InterimModuleBuilder.create("bbb2", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "1.5"))
                     .buildEntry())
-            .put(ModuleBuilder.create("bbb2", "1.1").buildEntry())
+            .put(InterimModuleBuilder.create("bbb2", "1.1").buildEntry())
             .put(
-                ModuleBuilder.create("bbb3", "1.0")
+                InterimModuleBuilder.create("bbb3", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "2.0"))
                     .addDep("bbb4", createModuleKey("bbb4", "1.1"))
                     .buildEntry())
             .put(
-                ModuleBuilder.create("bbb4", "1.0")
+                InterimModuleBuilder.create("bbb4", "1.0")
                     .addDep("ccc", createModuleKey("ccc", "3.0"))
                     .buildEntry())
-            .put(ModuleBuilder.create("bbb4", "1.1").buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.0", 1).buildEntry())
-            .put(ModuleBuilder.create("ccc", "1.5", 1).buildEntry())
-            .put(ModuleBuilder.create("ccc", "2.0", 2).buildEntry())
-            .put(ModuleBuilder.create("ccc", "3.0", 3).buildEntry())
+            .put(InterimModuleBuilder.create("bbb4", "1.1").buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.0", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "1.5", 1).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "2.0", 2).buildEntry())
+            .put(InterimModuleBuilder.create("ccc", "3.0", 3).buildEntry())
             .buildOrThrow();
     ImmutableMap<String, ModuleOverride> overrides =
         ImmutableMap.of(
@@ -800,10 +1181,10 @@
     //     \            \-> bbb4@1.1
     //     \-> bbb4@1.1
     // ccc@1.5 and ccc@3.0, the versions violating the allowlist, are gone.
-    BazelModuleResolutionValue selectionResult = Selection.run(depGraph, overrides);
+    Selection.Result selectionResult = Selection.run(depGraph, overrides);
     assertThat(selectionResult.getResolvedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", Version.EMPTY)
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb1", createModuleKey("bbb1", "1.0"))
                 .addDep("bbb2", createModuleKey("bbb2", "1.1"))
@@ -812,23 +1193,23 @@
                 .addDep("bbb4", createModuleKey("bbb4", "1.1"))
                 .addOriginalDep("bbb4", createModuleKey("bbb4", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb1", "1.0")
+            InterimModuleBuilder.create("bbb1", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "1.0"))
                 .addDep("bbb2", createModuleKey("bbb2", "1.1"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb2", "1.1").buildEntry(),
-            ModuleBuilder.create("bbb3", "1.0")
+            InterimModuleBuilder.create("bbb2", "1.1").buildEntry(),
+            InterimModuleBuilder.create("bbb3", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .addDep("bbb4", createModuleKey("bbb4", "1.1"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb4", "1.1").buildEntry(),
-            ModuleBuilder.create("ccc", "1.0", 1).buildEntry(),
-            ModuleBuilder.create("ccc", "2.0", 2).buildEntry())
+            InterimModuleBuilder.create("bbb4", "1.1").buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.0", 1).buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0", 2).buildEntry())
         .inOrder();
 
     assertThat(selectionResult.getUnprunedDepGraph().entrySet())
         .containsExactly(
-            ModuleBuilder.create("aaa", Version.EMPTY)
+            InterimModuleBuilder.create("aaa", Version.EMPTY)
                 .setKey(ModuleKey.ROOT)
                 .addDep("bbb1", createModuleKey("bbb1", "1.0"))
                 .addDep("bbb2", createModuleKey("bbb2", "1.1"))
@@ -837,25 +1218,25 @@
                 .addDep("bbb4", createModuleKey("bbb4", "1.1"))
                 .addOriginalDep("bbb4", createModuleKey("bbb4", "1.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb1", "1.0")
+            InterimModuleBuilder.create("bbb1", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "1.0"))
                 .addDep("bbb2", createModuleKey("bbb2", "1.1"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb2", "1.0")
+            InterimModuleBuilder.create("bbb2", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "1.5"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb2", "1.1").buildEntry(),
-            ModuleBuilder.create("bbb3", "1.0")
+            InterimModuleBuilder.create("bbb2", "1.1").buildEntry(),
+            InterimModuleBuilder.create("bbb3", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "2.0"))
                 .addDep("bbb4", createModuleKey("bbb4", "1.1"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb4", "1.0")
+            InterimModuleBuilder.create("bbb4", "1.0")
                 .addDep("ccc", createModuleKey("ccc", "3.0"))
                 .buildEntry(),
-            ModuleBuilder.create("bbb4", "1.1").buildEntry(),
-            ModuleBuilder.create("ccc", "1.0", 1).buildEntry(),
-            ModuleBuilder.create("ccc", "1.5", 1).buildEntry(),
-            ModuleBuilder.create("ccc", "2.0", 2).buildEntry(),
-            ModuleBuilder.create("ccc", "3.0", 3).buildEntry());
+            InterimModuleBuilder.create("bbb4", "1.1").buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.0", 1).buildEntry(),
+            InterimModuleBuilder.create("ccc", "1.5", 1).buildEntry(),
+            InterimModuleBuilder.create("ccc", "2.0", 2).buildEntry(),
+            InterimModuleBuilder.create("ccc", "3.0", 3).buildEntry());
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModuleTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModuleTest.java
index 3f65555..9dbb690 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModuleTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModuleTest.java
@@ -16,6 +16,7 @@
 package com.google.devtools.build.lib.bazel.bzlmod;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.buildModule;
 import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.buildTag;
 import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createModuleKey;
 import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createTagClass;
@@ -84,9 +85,7 @@
                                 .build())))
             .build();
     Module module =
-        Module.builder()
-            .setName("foo")
-            .setVersion(Version.parse("1.0"))
+        buildModule("foo", "1.0")
             .setKey(createModuleKey("foo", ""))
             .addDep("bar", createModuleKey("bar", "2.0"))
             .build();
@@ -128,12 +127,7 @@
     ModuleExtensionUsage usage = getBaseUsageBuilder().addTag(buildTag("blep").build()).build();
     ModuleExtension extension =
         getBaseExtensionBuilder().setTagClasses(ImmutableMap.of("dep", createTagClass())).build();
-    Module module =
-        Module.builder()
-            .setName("foo")
-            .setVersion(Version.parse("1.0"))
-            .setKey(createModuleKey("foo", ""))
-            .build();
+    Module module = buildModule("foo", "1.0").setKey(createModuleKey("foo", "")).build();
     AbridgedModule abridgedModule = AbridgedModule.from(module);
 
     ExternalDepsException e =