diff --git a/r11k/interval.py b/r11k/interval.py
index c51ffc353600c8258b0891033e227c64aee3baad..a29971ec590d6dd60d196cc09d308daba801caf7 100644
--- a/r11k/interval.py
+++ b/r11k/interval.py
@@ -49,8 +49,8 @@ class Interval:
     def __init__(self,
                  minimi: VersionInfo = min_version,
                  maximi: VersionInfo = max_version,
-                 min_cmp: Comp = operator.gt,
-                 max_cmp: Comp = operator.le,
+                 min_cmp: Comp = operator.ge,
+                 max_cmp: Comp = operator.lt,
                  *,
                  by: Optional[str] = None,
                  min_by: Optional[str] = None,
@@ -197,7 +197,7 @@ def intersect(a: Interval, b: Interval) -> Interval:
     max_cmp: Comp
 
     if not overlaps(a, b):
-        raise ValueError("Only overlapping intervals can intersect")
+        raise ValueError(f"Only overlapping intervals can intersect, {a}, {b}")
 
     # Lower Bound
     if a.minimi == b.minimi:
diff --git a/r11k/puppetfile.py b/r11k/puppetfile.py
index 72b2938a247401e69af05cf89cf36489e40e5331..19cbdf56baeeabe42742d2d15eab981c2b8bb6fe 100644
--- a/r11k/puppetfile.py
+++ b/r11k/puppetfile.py
@@ -46,6 +46,7 @@ modules:
 """
 
 import logging
+import os
 import os.path
 
 from dataclasses import dataclass, field
@@ -69,6 +70,7 @@ from r11k.puppetmodule import (
 
 
 logger = logging.getLogger(__name__)
+logger.setLevel(logging.DEBUG)
 
 
 @dataclass
@@ -251,6 +253,90 @@ def update_module_names(modules: list[PuppetModule]) -> None:
             module.name = metadata.name
 
 
+def build_initial_constraints(modules: list[PuppetModule]) -> dict[str, list[Interval]]:
+    """
+    Build the initial constraint set.
+
+    Takes a list of puppet module information. Returns a mapping from
+    each modules name, to a list of all constraints placed on that
+    module.
+    """
+    # Dict from which modules we want, to which versions we can have
+    constraints: dict[str, list[Interval]] = {}
+
+    # For each module in puppetfile
+    for module in modules:
+        logger.debug(module)
+        # collect its dependencies
+        for depspec in module.metadata.dependencies:
+            # and merge them to the dependency set
+            constraints.setdefault(depspec.name, []) \
+                       .append(depspec.interval(by=module.name))
+    return constraints
+
+
+def resolve_constraints(constraints: dict[str, list[Interval]]) -> dict[str, Interval]:
+    """
+    Merge all constraints on a module to a final constraint.
+
+    For each module, find the intersection of all modules. If none
+    intersection can be found a warning is raised, and the interval
+    from the smallest minimum requested to the largest value requested
+    is used.
+    """
+    # resolve constraints
+    resolved_constraints: dict[str, Interval] = {}
+    for module_name, intervals in constraints.items():
+        try:
+            resolved_constraints[module_name] = reduce(interval.intersect, intervals)
+        except ValueError:
+            print("Print invalid constraints for", module_name, ":")
+            for intv in intervals:
+                print('   ', intv)
+            inter = Interval(min(i.minimi for i in intervals),
+                             max(i.maximi for i in intervals),)
+            print("Defaulting to", inter)
+            resolved_constraints[module_name] = inter
+
+    return resolved_constraints
+
+
+def build_next_module_set(puppetfile: PuppetFile,
+                          known_modules: dict[str, PuppetModule],
+                          resolved_constraints: dict[str, Interval]) -> list[PuppetModule]:
+    # build the next iteration of modules
+    # If this turns out to be identical to modules, then
+    # everything is resolved and we exit. Otherwise we continue
+    # the loop
+    next_modules: dict[str, PuppetModule] = {}
+    for name, interval_ in resolved_constraints.items():
+        if module_ := known_modules.get(name):
+            # An explicitly specified version is always used.
+            # TODO warn if that version is incompatible with our interval.
+            next_modules[name] = module_
+        else:
+            # If we haven't explicitly stated the module, assume that
+            # it's to be downloaded from the puppet forge.
+            # TODO possibly allow dependencies on other git modules.
+            #
+            module_ = ForgePuppetModule(name, config=puppetfile.config)
+            # TODO keep interval instead of locking it in
+            # TODO continue here, fix this
+            module_.version = interval_.newest(module_.versions())
+            next_modules[name] = module_
+            known_modules[name] = module_
+
+    # TODO what are we even forcing here?
+    for name, module_ in puppetfile.modules.items():
+        if x := next_modules.get(name):
+            if x.version != module_.version:
+                logger.warn('Forcing %s', name)
+            # print(x, module_)
+        next_modules[name] = module_
+
+    return list(next_modules.values())
+
+
 # TODO figure out proper way to propagate config through everything
 def find_all_modules_for_environment(puppetfile: PuppetFile) -> ResolvedPuppetFile:
     """
@@ -262,6 +348,7 @@ def find_all_modules_for_environment(puppetfile: PuppetFile) -> ResolvedPuppetFi
 
     update_module_names(modules)
 
+    # Explicitly specified modules.
     known_modules: dict[str, PuppetModule] = {}
     for module in modules:
         known_modules[module.name] = module
@@ -269,55 +356,18 @@ def find_all_modules_for_environment(puppetfile: PuppetFile) -> ResolvedPuppetFi
     # Resolve all modules, restarting with the new set of modules
     # repeatedly until we find a stable point.
     while True:
-        # Dict from which modules we want, to which versions we can have
-        constraints: dict[str, list[Interval]] = {}
-
-        # For each module in puppetfile
-        for module in modules:
-            logger.debug(module)
-            # collect its dependencies
-            for depspec in module.metadata.dependencies:
-                # and merge them to the dependency set
-                constraints.setdefault(depspec.name, []) \
-                           .append(depspec.interval(by=module.name))
-
-        # resolve constraints
-        resolved_constraints: dict[str, Interval] = {}
-        for module_name, intervals in constraints.items():
-            # TODO what to do if invalid constraints
-            resolved_constraints[module_name] = reduce(interval.intersect, intervals)
-
-        # build the next iteration of modules
-        # If this turns out to be identical to modules, then
-        # everything is resolved and we exit. Otherwise we continue
-        # the loop
-        next_modules: dict[str, PuppetModule] = {}
-        for name, interval_ in resolved_constraints.items():
-            if module_ := known_modules.get(name):
-                next_modules[name] = module_
-            else:
-                # TODO keep interval instead of locking it in
-                module_ = ForgePuppetModule(name, config=puppetfile.config)
-                # TODO this crashes when the dependency is a git
-                # module, since it tries to fetch a forge module of
-                # that name.
-                # TODO continue here, fix this
-                module_.version = interval_.newest(module_.versions())
-                next_modules[name] = module_
-                known_modules[name] = module_
-
-        # TODO what are we even forcing here?
-        for name, module_ in puppetfile.modules.items():
-            if next_modules.get(name):
-                logger.warn('Forcing %s', name)
-            next_modules[name] = module_
+        constraints = build_initial_constraints(modules)
+        resolved_constraints = resolve_constraints(constraints)
 
-        next_modules_list: list[PuppetModule] = list(next_modules.values())
+        next_modules: list[PuppetModule] = build_next_module_set(puppetfile,
+                                                                 known_modules,
+                                                                 resolved_constraints)
 
-        if next_modules_list == modules:
+        if next_modules == modules:
             break
 
-        modules = next_modules_list
+        modules = next_modules
+        print()
 
     return ResolvedPuppetFile(modules={m.name: m for m in modules},
                               environment_name=puppetfile.environment_name,
diff --git a/r11k/puppetmodule/git.py b/r11k/puppetmodule/git.py
index e99008c83c67da8001db7b94c315aaa2cd9fbd6c..f2ddbf75e87cd20630a2e2f22379ea1c56717a8d 100644
--- a/r11k/puppetmodule/git.py
+++ b/r11k/puppetmodule/git.py
@@ -157,6 +157,7 @@ class GitPuppetModule(PuppetModule):
 
         remote = find_remote(repo.remotes, git_url)
         if not remote:
+            print(repo)
             raise ValueError('Existing repo, but with different remote')
 
         last_modified = repo_last_fetch(repo)