By Herodion Victor Momoh
A few months into one of my projects, I noticed that a regular utility file that was only supposed to handle date formatting had somehow grown imports from five different modules it had no business touching. We didn’t design it to do that, nor did we approve it to do that.
That was my first real experience with the concept of architecture drift. Which is basically what happens when there is a mismatch between the intention that created the code your team wrote and the effects or functions of that code when it ships. And the uncomfortable thing about it is that nobody is usually doing anything wrong when it happens. Engineers are just solving the problem in front of them using whatever is closest to hand.
The reason I am writing this article is that I want to highlight how and why this happens, specifically in TypeScript projects. And this is because, as any developer who’s pretty familiar with TypeScript will know, architectural drift happens far more often in TypeScript projects than in any other project.
The most prominent feature of TypeScript is that it gives you module imports that are explicit and readable, and that also feels like a safety net. You can see what depends on what.
The core challenge here, however, is that the compiler, which is your core machine code reader, doesn’t necessarily know your team’s architectural rules. If the instructions are not written into the code, it allows anything to spill over into compartments they’re not supposed to reach.
This creates situations where a component in your UI layer can pull things directly out of your database access code, and TypeScript, oblivious of the inappropriateness of that, allows it to happen. The architecture is broken, but because they match, it sees the build as fine as far as it is concerned. This raises no flags, and it just keeps happening. That’s where the problem lies.
The reason why this issue differs from ordinary technical debt is that it’s structural. When your code gets messy inside a single file, it can be fixed by a refactor. However, when the boundaries between your layers have quietly collapsed over six months of small decisions, you definitely can’t afford to postpone fixing it.
The problem was simply too infuriatingly recurrent in my projects, and I couldn’t find anything that addressed it directly. And this is why I started building Driftlog as a solution to this problem. Driftlog is an architecture drift monitoring tool for TypeScript codebases. The core idea is pretty straightforward.
To clarify the basics, your codebase is a collection of modules. And modules import from each other, so you can read all those imports and build a map of what depends on what.
After that, the next step is to write down your architectural rules as constraints on that map and feed it to your compiler. This includes instructions like “the UI layer cannot import from the database layer” or “the payments module should not know anything about the notifications module”. What this does is ensure that every pull request is checked against those rules. So, when something drifts, you find out before it ships.
Driftlog uses something called ts-morph to do this. ts-morph is a library that lets you read TypeScript code as structured data. So, instead of scanning files manually, you can ask the codebase questions. Driftlog traces how sections in your project reference each other, builds a connection map, and compares that map against your architectural instructions. I know how that sounds. Dependency graphs are not some new concept anybody invented recently. But the thing is, most of the tools that use them are asking whether your code is good. What Driftlog is saying is that your system still looks the way you designed it. It’s a solution nobody has set out to solve.
The other thing I kept running into is the need to solve how much time debugging this issue eats into engineering time. Whenever a request is pulled and a senior engineer spots that a component is reaching into a module it shouldn’t, and they leave a comment, and the developer fixes it, and that’s that.
And that works, most times, if the engineers still remember the decisions that were made eighteen months ago, and actually have enough spare time to catch everything moving through the review queue. Which in practice is maybe three or four weeks out of twelve. The rest of the time the boundary gets crossed because there was a deadline and catching it would have taken longer than just letting it go.
Measuring drift doesn’t solve the problem automatically. But it does change the conversation. Right now, when an engineer makes a boundary violation, there’s usually no feedback until a senior engineer catches it by chance. With something like Driftlog, the feedback is immediate and specific. “This import crosses a boundary your team defined. Here’s what you imported, here’s where it came from, here’s the rule it violated.” That’s a very different experience from discovering the problem nine months later when you’re trying to understand why a change in the billing module broke something in the authentication flow.
Just so we’re clear, Driftlog is still early. The research behind it and the production systems I’ve worked on are the more concrete output at this point. PathIQ in particular is where I designed the supplier integration layer specifically to stop this kind of drift. This was our sandbox where we tested our ideas against reality.
The broader case I’m making is that architecture should be a continuous property of a codebase, not a one-time decision you make at the start and then hope survives real interactions. The software industry has figured out how to measure almost everything else that matters. We’re talking performance, reliability, test coverage, security vulnerabilities, etc. The structural integrity of the system, whether it still looks like what it was designed to look like, somehow never made it onto that list.
It should. And the tools to make it measurable already basically exist. Someone just has to decide it’s worth tracking.


