← The ADLC library
Failure modes · 8

Cross-team dependencies nobody declared

The dependency was real, mutual, and invisible. Both teams found out about it in the same week, from the same incident, four days before a launch.

Two teams. One builds the customer-facing checkout. The other owns a shared pricing service that half a dozen products consume. They are in different parts of the org chart, they have different planning cycles, and they talk maybe once a month.

The checkout team is building a promotions feature. It needs pricing to expose discount metadata that pricing does not currently return. Someone from checkout raised this in a channel eight weeks ago, got a thumbs up from a pricing engineer, and moved on. No ticket was created on the pricing side, because the conversation felt like it had resolved.

Meanwhile the pricing team is doing an unrelated piece of work: consolidating three response shapes into one. It is good work, overdue, and it removes a field that checkout has been reading for two years without pricing knowing, because the field was undocumented and checkout picked it up from an example payload.

So there are two dependencies here. Checkout depends on pricing to add something. Pricing is about to break something checkout already depends on. Neither is written down anywhere. Neither team’s board contains a single item that references the other team.

Both surface in the same week. The consolidation ships to staging on a Tuesday, checkout’s integration tests fail on Wednesday in a way that takes a day to diagnose because the failure is three layers from the cause, and on Thursday somebody finally asks the pricing team what changed. The promotions launch is the following Monday. It does not happen on the following Monday.

Checkout needs a fieldRaised in a channel eight weeks ago, acknowledged, never recorded anywhere.
Pricing consolidates responsesGood work, overdue, and it removes a field checkout has been reading undocumented.
Same week, both surfaceStaging Tuesday, failing tests Wednesday, a day to diagnose because the failure is three layers from the cause.
Two dependencies pointing in opposite directions. Neither written down, neither team at fault, and both discoverable from the code months earlier.

Declared dependencies are the ones that never hurt you

Every organisation has a process for dependencies. Someone maintains a dependency register, or there is a section in the planning template, or there is a quarterly cross-team planning session where representatives declare what they need from each other.

I do not think these are useless. I think they are systematically aimed at the wrong dependencies.

A dependency that gets declared has already been noticed by someone with enough context to see it and enough forethought to raise it. That is a dependency somebody is thinking about. Those get managed. They slip sometimes, and there is friction, but they rarely produce the four-days-before-launch discovery, because they exist in at least one person’s head as a live concern.

The ones that hurt share a property: nobody knew they existed. Not “nobody raised it,” which implies negligence. Nobody knew. The checkout team did not know they were reading an undocumented field, because whoever wrote that code left, and the field worked. The pricing team did not know anyone was reading it, because their consumers are not enumerated anywhere and the only way to find out would have been to ask everyone.

You cannot declare what you do not know. So a process built entirely on declaration has coverage over exactly the population of dependencies that was already safe, and zero coverage over the population that causes incidents. That is a strange thing to run an organisation on, and yet almost everyone does.

A dependency that gets declared has already been noticed by someone with enough context to see it and enough forethought to raise it. Those were never the dangerous ones.

So a process built entirely on declaration has full coverage of the population that was already safe, and zero coverage of the population that hurts.

The four kinds, and only one is manageable by declaration

It helps to separate them.

Known and declared. Managed adequately by existing process. Not the problem.

Known but not declared. The channel conversation with a thumbs up. Somebody knows, but the knowledge lives in a message rather than in any system, and it is not attached to either team’s plan. These are recoverable by process discipline, and this is where most dependency-management effort goes. Fair enough, but it is the smaller half.

Known by one side only. Checkout knows it needs the discount metadata; pricing has forgotten the conversation and has no item for it. Asymmetric knowledge is worse than mutual ignorance, because the side that knows assumes it is handled and stops watching. Half of the late discoveries I have seen are this shape: somebody was confident, and their confidence was based on a conversation the other party does not remember.

Known by nobody. The undocumented field. This is the one that produces the genuinely expensive failures, and no amount of planning ceremony surfaces it, because there is no human in the loop who could raise it.

The fourth category is the one worth building for, and it is the only one where the evidence lives somewhere other than in people’s heads. It lives in the code. Checkout’s repository contains a read of a field that pricing’s repository produces. That relationship is a fact, sitting in two repositories, discoverable without asking anyone anything.

Known and declared

Managed adequately by existing process. Not the problem, and the only thing the register covers.

Known but not declared

A channel conversation with a thumbs up. The knowledge lives in a message, attached to neither team's plan.

Known by one side only

Asymmetric knowledge, worse than mutual ignorance, because one side is confidently planning on it.

Known by nobody

The undocumented field. No ceremony surfaces it, because no human is holding it. But the code is.

Only the fourth category has its evidence somewhere other than in people's heads, which is precisely why it is the one worth building for.

Agents make the fourth category bigger

I want to be careful not to blame agents for a problem that predates them by decades. Undocumented coupling is as old as APIs. But there are two specific ways the current shift makes it worse, and both are structural rather than incidental.

Reach. An agent implementing a feature will follow the code wherever the code goes, including into parts of the system the assigned engineer has never opened. That is often a good thing. It also means the set of things a piece of work touches is no longer bounded by one person’s familiarity, which was, quietly, one of the main mechanisms limiting accidental coupling. Engineers used to avoid unfamiliar subsystems partly out of caution. That caution was a coupling limiter and nobody thanked it.

Speed asymmetry between building and coordinating. Writing the integration got much faster. Having the conversation with the other team did not. So the ratio between “time to build something that depends on another team” and “time to negotiate that dependency” has moved sharply, and the rational local choice is increasingly to build first and coordinate later, or not at all. When integrating against an undocumented field takes twenty minutes and getting it documented takes three weeks, you can predict which happens.

The combination means the volume of undeclared coupling is rising while the mechanisms for noticing it are unchanged.

What detection would actually look like

If declaration cannot cover the dangerous category, the alternative is inference from ground truth. Some of this is genuinely tractable and some of it is harder than vendors like to admit.

The tractable parts:

Shared surface changes. When a repository changes a symbol, endpoint, schema field, or event shape that another repository references, that is a mechanically detectable relationship. This is the highest-value signal available and it would have caught the removed field.

Work touching the same code from different teams. Two teams with open changes in the same files or the same service, in the same window, are coupled whether or not they know it. You do not need to understand the semantics to know that this warrants a conversation.

Ticket text referencing another team’s domain. Weaker, but cheap. “Needs pricing to return discount metadata” is a sentence sitting in a ticket description, unread by anyone on the pricing team. Text signal alone is noisy, but combined with a repository relationship it becomes meaningful.

Blocked-in-place work. A ticket that has been in progress with no branch activity for two weeks is often blocked on something external, and the something external is frequently another team. Stall detection is a decent proxy for undeclared dependency even without knowing what the dependency is.

The harder parts, which I would be sceptical of any confident claim about:

Semantic coupling that does not appear in code, like two teams depending on the same assumption about how a value behaves. Data coupling through a shared store where neither side references the other’s code. Timing dependencies, where the ordering of two deployments matters and nothing in either repository expresses that. Human dependencies on a specific person’s availability, which are extremely real and completely invisible to any code-reading system.

An honest dependency-detection story covers the first list and admits the second. GroundTruth scores cross-team dependency as one input to delivery risk, and the useful framing is that it raises the probability of a conversation happening, not that it produces a complete graph. Nobody produces a complete graph.

The conversation is still the deliverable

Something worth stating plainly, because it is easy to lose in a discussion about detection: identifying a dependency does not resolve it.

The value of finding the removed field on the Tuesday of the previous week is not that a machine handled it. It is that a human on checkout and a human on pricing had a fifteen-minute conversation with two weeks of runway instead of four days. That conversation was always going to be necessary. Detection only changes when it happens.

This matters because it sets a realistic bar for what the tooling is for. If you evaluate dependency detection by asking “did it prevent the incident,” the answer will often be no, because incidents are prevented by people making decisions. If you evaluate it by asking “did the right two people find out early enough to have a cheap conversation instead of an expensive one,” you are measuring the thing that actually varies.

It also sets a bar for the failure mode of over-detection. A system that surfaces forty possible dependencies a week does not produce forty conversations. It produces zero, plus a filter rule. The precision of the signal is not a nice-to-have, it is the entire product, because the output is human attention and human attention has a very low tolerance for false positives.

Where this breaks down

Four honest limits.

Most detected coupling does not matter. Two teams touching the same file in the same week is usually nothing. Shared surface changes are usually additive and safe. If you surface every detected relationship, you will produce a firehose whose signal-to-noise ratio is bad enough that it gets ignored within a month, and you will have burned the team’s willingness to look at dependency alerts for a year. The bar for surfacing should be uncomfortably high: a removal or a breaking change to a surface with a known external consumer, not “these two things are related.”

The organisational fix may be better than the detection fix. If two teams are discovering coupling this way repeatedly, that is often evidence of a boundary problem rather than a communication problem. A service with six consumers and no documented contract, no consumer tests, and no deprecation process will generate this failure forever regardless of how good your detection is. Detection treats the symptom continuously; a published contract with consumer-driven tests treats the cause once. I would rather a team spent a quarter on the contract than on a dashboard, and there is a real risk that good detection tooling makes the underlying architectural problem comfortable enough to ignore.

Some dependencies should be broken, not managed. The undocumented field read is not really a dependency to be coordinated. It is a coupling that should not exist. There is a version of dependency management that formalises and preserves bad coupling, by turning every accidental relationship into a tracked, negotiated, permanent one. That makes the system more rigid, not safer. Sometimes the right answer to “checkout depends on this internal pricing field” is that checkout should stop.

And detection cannot see the dependencies that live in people. The most common blocking dependency in most organisations is one person’s time: the only reviewer for a subsystem, the only person who understands the migration, the person on leave. No repository-reading system will find that, and it is probably a bigger source of delay than any code-level coupling. Be wary of any dependency picture that looks complete, because the part it is missing is the part that most often ruins a plan.

The takeaway

Dependency processes are built on declaration, and declaration only covers dependencies somebody already knows about, which are the ones that were never going to hurt you. The expensive category is the one nobody knows exists, and its evidence is not in anyone’s head, it is sitting in two repositories.

So the move is to infer what can be inferred from ground truth, hold a very high bar for what gets surfaced, and judge the result by whether the right two people had a cheap conversation early rather than by whether an incident was prevented. And keep asking whether the coupling you keep detecting should be managed better or removed entirely.

The next piece turns the argument of this whole series against itself and asks when automation makes things worse: what happens when a system that reads ground truth starts writing it back, and why more automation is frequently the wrong answer.