Why it matters: AI assistants routinely ship code that looks decoupled but still hard-wires your domain logic to specific vendors, and the trap only surfaces months later when you need a second provider.
The Details:
- Ask an assistant to decouple two components and it almost always reaches for callbacks, event emitters, or hook registries. These flip who calls whom at runtime, which is inversion of control. They do nothing to change which way your source files import from each other, which is the separate, harder problem of dependency inversion.
- A typical failure: an order module gets a payment callback, tests pass, the diff looks clean. But the order module still imports vendor-specific types like a charge object or payment intent. Swap providers or add a compliance rule and you discover the coupling never left, it just moved one layer deeper into the callback.
- The real fix is ownership of vocabulary, not ownership of the call stack. The high-level module defines its own interface and result type in its own terms; the vendor adapter imports that interface and translates. Then the domain code never mentions the vendor at all.
- The bias toward control-flow tricks is structural. Callbacks and hooks fill training data because finished codebases show the pattern everywhere. Interface-ownership decisions are nearly invisible in that same data, since by the time code exists, the boundary argument that produced it has already disappeared. Tickets also read as additive requests to wire something up, not as a mandate to reorganize imports, so the model never shifts into that mode unasked.
Bottom Line: Define what a module owns and what it may never import before you prompt for feature work, then check the import graph after, because passing tests prove behavior, not architecture.