What this is is really illustrating is some mismatch between my expectations of how lake works and how it actually works. It has nothing to do with the module system.
$ lake clean
$ git checkout good
Switched to branch 'good'
$ lake build Moduletest
Build completed successfully (4 jobs).
$ git checkout bad
Switched to branch 'bad'
$ lake build Moduletest
Build completed successfully (3 jobs).
$ lake build
✖ [6/6] Building moduletest:exe
...
The diff between good and bad is
$ git diff good bad
diff --git a/Dep.lean b/Dep.lean
index d094278..48996b3 100644
--- a/Dep.lean
+++ b/Dep.lean
@@ -1 +1 @@
-def my_constant : Nat := 42
+def my_constant_bad : Nat := 42
diff --git a/lakefile.toml b/lakefile.toml
index 0f5b190..5e8a34f 100644
--- a/lakefile.toml
+++ b/lakefile.toml
@@ -4,7 +4,7 @@ defaultTargets = ["moduletest"]
[[lean_lib]]
name = "Moduletest"
-roots = ["Moduletest", "Dep"]
+roots = ["Moduletest"]
[[lean_exe]]
name = "moduletest"
When Dep is removed from roots, lake no longer presumes to manage
Dep, and so does not recompile it, but it does pull in the
previously compiled copy when Moduletest.lean imports it.