The Contract Nobody Signed
Three different bugs, one missing thing in common - an actual agreement between whoever produces a dataset and whoever depends on it.
A canonical tag drifting from a sitemap. A Type 2 dimension with an off-by-one boundary. A metric quietly redefined by an app release nobody on the data side reviewed. Three unrelated-looking problems, three different root causes - and the same missing thing underneath all of them: nobody had written down, in a place both sides were obligated to check, what the data was actually supposed to be.
A data contract is that written-down thing. Not a legal document - an explicit agreement between whoever produces a dataset and whoever consumes it, covering what the data means, who's accountable for it, how it's allowed to change, and what happens when it breaks. Most teams don't have one. Most teams don't notice until something like the three problems above shows up first.
"Data contract" gets used loosely enough that it's worth being specific about what it actually needs to contain to do any good. It's fewer than four things, and none of them are exotic.
Every dataset that other teams depend on needs a named owner - not a team name, an actual person or role accountable for it. Without that, a breaking change ships from whoever happened to touch the code last, and consumers find out by their dashboard breaking rather than by being told.
A schema or a definition doesn't get changed in place - it gets versioned, the way an API does. A backwards-incompatible change ships as a new version consumers opt into on their own timeline; a compatible change gets documented and dated. The alternative is what a Type 2 boundary bug or a silently redefined metric both look like from the consumer's side: the same name, quietly meaning something different underneath.
Freshness, completeness, and accuracy need an explicit, agreed-on standard - how current the data is expected to be, what "complete" means for that specific dataset, and how quickly a violation gets acknowledged and fixed. Without a stated SLA, "the dashboard is a day behind" is either a non-issue or an outage depending entirely on who you ask, because nobody agreed in advance which one it is.
A change that affects a contract - a column, a firing condition, a definition - needs to require sign-off from the consumer side before it ships, not just a notification after the fact. This is the single piece that would have caught the semantic drift case directly: the event's firing condition changed inside a release that never crossed paths with anyone who reported on it.
Nobody sets out to skip it. A dataset starts as an internal implementation detail with one consumer who sits three desks away, and an informal handshake is genuinely enough. It stays informal past the point where it's still enough, because nothing forces a review of that decision - the data keeps flowing, the dashboards keep rendering, right up until a producer-side change crosses a boundary nobody had drawn.
By the time a contract would obviously help, there are usually several consumers, at least one of whom nobody producing the data has ever spoken to directly. Retrofitting ownership and review gates at that point is a bigger lift than it would have been at the start - which is exactly why it's worth treating as a default from the first consumer, not a response to the third incident.
None of this requires new tooling to start. In most cases, it's:
The goal isn't paperwork for its own sake. It's making sure that when a definition changes, it changes as a decision both sides made together, instead of an accident one side discovers later. Establishing that discipline is part of the Analytics & Data work done alongside every new integration.
The formal version scales with team size, but the underlying discipline applies as soon as there's more than one consumer of a dataset who isn't also its producer - which happens well before an organization would call itself "large-scale."
Schema documentation describes structure. A contract also covers ownership, versioning behavior, and freshness expectations - structure was never the part that failed in any of the incidents that make this worth doing; the agreement around how it's allowed to change was.
A named owner and a one-paragraph definition for each dataset other people depend on, plus an agreement that changes to either get communicated before they ship rather than discovered after. That alone catches most of what actually causes incidents like the ones documented elsewhere in these notes.
A short conversation is usually enough to tell whether there's a quick answer or something deeper worth tracing.