The Cycle Neither Module Will Give Up
Decide what you would do from the brief alone, including whether you would change anything at all. Everything below it is available, but the exercise stops working if you open it first.
booking imports billing to ask whether a cancellation falls inside the fee window; billing imports booking to read the appointment time it needs to answer. The build tolerates it and the IDE warns. A new engineer's attempt to break the cycle produced an undefined at startup.
Breaking the cycle by declaring BillingWindowProvider in booking and having billing implement it. The import graph is acyclic, the linter is satisfied, the dependency-inversion box is ticked, and the change is small — four experienced engineers will approve it. Nothing about the reasoning changed: understanding cancellation still requires reading both modules, and now an interface that exists only to hide the arrow. The variant that hurts more slowly is moving one of the two classes into shared/, which converts a two-module cycle into a module that every module depends on. Inverting an arrow renames a cycle; it does not give the rule an owner.
Read this even if you are confident. It is here rather than behind a button because it is the answer most teams actually ship, it passes review, and the cost of it does not arrive until the change after this one.