diff --git a/crates/ty_python_semantic/src/module_resolver/import-resolution-diagram.dot b/crates/ty_python_semantic/src/module_resolver/import-resolution-diagram.dot
new file mode 100644
index 0000000000..04dd3d5332
--- /dev/null
+++ b/crates/ty_python_semantic/src/module_resolver/import-resolution-diagram.dot
@@ -0,0 +1,141 @@
+// This is a Dot representation of a flow diagram meant to describe Python's
+// import resolution rules. This particular diagram starts with one particular
+// search path and one particular module name. (Typical import resolution
+// implementation will try multiple search paths.)
+//
+// This diagram also assumes that stubs are allowed. The ty implementation
+// of import resolution makes this a configurable parameter, but it should
+// be straight-forward to adapt this flow diagram to one where no stubs
+// are allowed. (i.e., Remove `.pyi` checks and remove the `package-stubs`
+// handling.)
+//
+// This flow diagram exists to act as a sort of specification. At the time
+// of writing (2025-07-29), it was written to capture the implementation of
+// resolving a *particular* module name. We wanted to add another code path for
+// *listing* available module names. Since code reuse is somewhat difficult
+// between these two access patterns, I wrote this flow diagram as a way of 1)
+// learning how module resolution works and 2) to provide a "source of truth"
+// that we can compare implementations to.
+//
+// To convert this file into an actual image, you'll need the `dot` program
+// (which is typically part of a `graphviz` package in a Linux distro):
+//
+// dot -Tsvg import-resolution-diagram.dot > import-resolution-diagram.svg
+//
+// And then view it in a web browser (or some other svg viewer):
+//
+// firefox ./import-resolution-diagram.svg
+//
+// [Dot]: https://graphviz.org/doc/info/lang.html
+
+digraph python_import_resolution {
+ labelloc="t";
+ label=<
+ Python import resolution flow diagram for a single module name in a single "search path"
+ (assumes that the module name is valid and that stubs are allowed)
+ >;
+
+ // These are the final affirmative states we can end up in. A
+ // module is a regular `foo.py` file module. A package is a
+ // directory containing an `__init__.py`. A namespace package is a
+ // directory that does *not* contain an `__init__.py`.
+ module [label="Single-file Module",peripheries=2];
+ package [label="Package",peripheries=2];
+ namespace_package [label="Namespace Package",peripheries=2];
+ not_found [label="Module Not Found",peripheries=2];
+
+ // The final states are wrapped in a subgraph with invisible edges
+ // to convince GraphViz to give a more human digestible rendering.
+ // Without this, the nodes are scattered every which way and the
+ // flow diagram is pretty hard to follow. This encourages (but does
+ // not guarantee) GraphViz to put these nodes "close" together, and
+ // this generally gets us something grokable.
+ subgraph final {
+ rank = same;
+ module -> package -> namespace_package -> not_found [style=invis];
+ }
+
+ START [label=<START>];
+ START -> non_shadowable;
+
+ non_shadowable [label=<
+ Is the search path not the standard library and
+ the module name is `types` or some other built-in?
+ >];
+ non_shadowable -> not_found [label="Yes"];
+ non_shadowable -> stub_package_check [label="No"];
+
+ stub_package_check [label=<
+ Is the search path in the standard library?
+ >];
+ stub_package_check -> stub_package_set [label="No"];
+ stub_package_check -> determine_parent_kind [label="Yes"];
+
+ stub_package_set [label=<
+ Set `module_name` to `{top-package}-stubs.{rest}`
+ >];
+ stub_package_set -> determine_parent_kind;
+
+ determine_parent_kind [label=<
+ Does every parent package of `module_name`
+ correspond to a directory that contains an
+ `__init__.py` or an `__init__.pyi`?
+ >];
+ determine_parent_kind -> maybe_package [label="Yes"];
+ determine_parent_kind -> namespace_parent1 [label="No"];
+
+ namespace_parent1 [label=<
+ Is the direct parent package
+ a directory that contains
+ an `__init__.py` or `__init__.pyi`?
+ >];
+ namespace_parent1 -> bail [label="Yes"];
+ namespace_parent1 -> namespace_parent2 [label="No"];
+
+ namespace_parent2 [label=<
+ Does the direct parent package
+ have a sibling file with the same
+ basename and a `py` or `pyi` extension?
+ >];
+ namespace_parent2 -> bail [label="Yes"];
+ namespace_parent2 -> namespace_parent3 [label="No"];
+
+ namespace_parent3 [label=<
+ Is every parent above the direct
+ parent package a normal package or
+ otherwise satisfy the previous two
+ namespace package requirements?
+ >];
+ namespace_parent3 -> bail [label="No"];
+ namespace_parent3 -> maybe_package [label="Yes"];
+
+ maybe_package [label=<
+ After replacing `.` with `/` in module name,
+ does `{path}/__init__.py` or `{path}/__init__.pyi` exist?
+ >];
+ maybe_package -> package [label="Yes"];
+ maybe_package -> maybe_module [label="No"];
+
+ maybe_module [label=<
+ Does `{path}.py` or `{path}.pyi` exist?
+ >];
+ maybe_module -> module [label="Yes"];
+ maybe_module -> maybe_namespace [label="No"];
+
+ maybe_namespace [label=<
+ Is `{path}` a directory?
+ >];
+ maybe_namespace -> namespace_package [label="Yes"];
+ maybe_namespace -> bail [label="No"];
+
+ bail [label=<
+ Is `module_name` set to a stub package candidate?
+ >];
+ bail -> not_found [label="No"];
+ bail -> retry [label="Yes"];
+
+ retry [label=<
+ Reset `module_name` to original
+ >];
+ retry -> determine_parent_kind;
+}
diff --git a/crates/ty_python_semantic/src/module_resolver/import-resolution-diagram.svg b/crates/ty_python_semantic/src/module_resolver/import-resolution-diagram.svg
new file mode 100644
index 0000000000..7584e5134f
--- /dev/null
+++ b/crates/ty_python_semantic/src/module_resolver/import-resolution-diagram.svg
@@ -0,0 +1,296 @@
+
+
+
+
+
diff --git a/crates/ty_python_semantic/src/module_resolver/resolver.rs b/crates/ty_python_semantic/src/module_resolver/resolver.rs
index a70f48ba23..a29af76c2c 100644
--- a/crates/ty_python_semantic/src/module_resolver/resolver.rs
+++ b/crates/ty_python_semantic/src/module_resolver/resolver.rs
@@ -1,3 +1,13 @@
+/*!
+This module principally provides two routines for resolving a particular module
+name to a `Module`: [`resolve_module`] and [`resolve_real_module`]. You'll
+usually want the former, unless you're certain you want to forbid stubs, in
+which case, use the latter.
+
+For implementors, see `import-resolution-diagram.svg` for a flow diagram that
+specifies ty's implementation of Python's import resolution algorithm.
+*/
+
use std::borrow::Cow;
use std::fmt;
use std::iter::FusedIterator;