The Crash Was Load-Bearing: How LLM Tolerance Hides Broken Data Contracts
For fifty years, data pipelines enforced their contracts by dying. An upstream team renamed a column, the downstream parser threw, the job crashed, someone got paged at 2 a.m., and by morning the contract was either fixed or formally renegotiated. Nobody designed this as a governance mechanism. It just fell out of the fact that rigid code cannot process input it doesn't expect. The crash was the enforcement. The pager was the audit trail.
Then we put an LLM in the consumer seat, and the breakage stopped crashing.
A model reading a malformed record doesn't throw a parse error. It copes. A missing field becomes a plausible guess. A renamed field becomes a slightly wrong interpretation. A unit change — cents to dollars, UTC to local — becomes a confident answer that's off by a factor the model never mentions. The pipeline runs green end to end, the dashboards stay quiet, and the broken contract surfaces three weeks later as a diffuse quality complaint that nobody can bisect. We didn't remove the failure. We removed the signal.
Crashes Were Never a Bug in the Contract System
It's worth being precise about what the crash used to buy you, because it bought more than most teams realize.
A hard failure at a schema boundary is a localizing event. The stack trace names the field, the timestamp names the deploy, and the blast radius is bounded: bad data stopped at the parser instead of flowing into six downstream tables. It's also a forcing event. The producing team can't quietly ship a breaking change and move on, because the consuming team's incident lands on their doorstep within hours. Every schema migration meeting, every deprecation notice, every "please review our proposed field rename" email exists because someone, somewhere, learned that unannounced changes cause pages.
This is the same insight behind the long-running critique of Postel's robustness principle — "be liberal in what you accept, conservative in what you send." Decades of protocol experience, summarized in the IETF's own reconsideration of the principle, showed that liberal acceptance doesn't make systems robust; it lets malformed behavior become entrenched, because nothing ever tells the sender they're wrong. Every quirk you tolerate becomes a quirk you support forever. Strict parsers, by contrast, are how ecosystems stay honest: rejection is feedback.
LLMs are the most liberal parser ever deployed. That's precisely their value — they extract meaning from messy, underspecified, human-shaped input. But when you wire one into a pipeline as a consumer of structured data, you've installed Postel's law at maximum setting in the one place where strictness was doing load-bearing work.
What Coping Actually Looks Like
The failure modes are worth cataloging, because none of them resemble failure.
Missing fields become inferences. Your prompt template renders Customer tier: {tier} and the upstream service stopped sending tier last Tuesday. A traditional templating engine might throw on the missing key; more likely your code renders Customer tier: None or an empty string. The model doesn't stop. It infers tier from other context — the customer's spend history, their email domain — and is right often enough that support tickets don't spike. They just drift.
Renamed fields become misreadings. Upstream renames account_status to lifecycle_stage with new enum values. Your extraction prompt still asks about account status. The model finds lifecycle_stage: "dormant_reactivated" in the JSON blob you pass it and maps it to whatever seems closest in its own ontology. Sometimes that mapping matches what your business logic expects. Sometimes it doesn't. There is no log line either way.
Semantic changes pass through untouched. The subtlest case: the schema doesn't change at all, but the meaning does. revenue switches from gross to net. timestamp switches from event time to ingestion time. A strict system would break on this too late or never — but a strict system also wouldn't paper over the discrepancies it happens to notice. A model asked to summarize anomalies will smooth them: "revenue dipped slightly this period," it says, of a 40% definitional change.
Garbage becomes fluent garbage. Truncated JSON, double-encoded strings, mojibake from an encoding mismatch — inputs that would kill any deserializer get read by the model the way a human reads a water-damaged letter. It reconstructs. The reconstruction is plausible. Plausible is the problem.
In every case, the model is doing exactly what it was trained to do: produce the most likely useful output given messy input. The behavior isn't a defect in the model. It's a mismatch between the model's tolerance and your architecture's assumption that someone at this boundary is checking.
Why You Can't Bisect a Vibe
When a strict pipeline breaks, you get an incident with a start time. When an LLM pipeline absorbs a broken contract, you get something much worse: a quality regression with no edge.
The decline is diffuse — it only affects records where the missing field mattered, so aggregate metrics move by single digits. It's delayed — users notice the output feels off before any metric does, and by the time someone files a ticket, the upstream change is weeks old and three other deploys have shipped. And it's unattributable — when quality does measurably dip, the suspect list is long: a model version bump, a prompt edit, retrieval index staleness, seasonal input drift. An upstream team's field rename doesn't even make the list, because that team's change log lives in a different org's repo.
This is why "we'll catch it in evals" fails here. Your eval set was built from historical inputs — inputs that conform to the old contract. A broken upstream contract changes the distribution of production inputs, not the behavior of the model on your frozen test cases. Evals green, production degrading, and the gap between them is exactly the shape of the schema drift.
The teams that do catch these regressions usually catch them embarrassingly: a customer pastes two outputs side by side, one from before the change and one from after. That's your monitoring system now — customer memory.
Restoring the Crash: Validate Before Inference
The fix is conceptually simple and organizationally annoying: reintroduce strictness at the boundary, in front of the model, where the architecture lost it.
Concretely, that's a validation layer that runs before any tokens are spent:
- Schema validation with required fields, not just types. The contract should state which fields the prompt actually depends on, and their absence should be a rejection, not a
None. If your prompt template references twelve fields, your validator asserts twelve fields. This sounds obvious; almost nobody does it, because the prompt template and the ingestion code are owned by different people and the dependency is invisible. - Distribution and semantic checks on high-stakes fields. Null-rate spikes, enum values outside the known set, numeric fields whose scale shifts by an order of magnitude. These are standard data-quality checks — the point is wiring them as gates on the inference path, not as a nightly report someone skims.
- A dead-letter queue instead of a shrug. Records that fail validation don't get silently dropped and don't get heroically processed anyway. They get parked, counted, and alerted on. The dead-letter queue restores the property the crash used to provide: bad input becomes visible input, with a paper trail, without taking down the records that are still fine.
Notice what this layer really is: it's the crash, rebuilt as infrastructure. You're not making the model stricter — you can't, and you shouldn't want to. You're making sure that by the time data reaches the model, tolerance is a feature again instead of a cover-up.
The organizational half is harder. A validation layer is only as good as the contract it validates, and LLM consumers have a new kind of dependency to declare: the prompt itself. When a prompt references a field, that's a consumer contract, and it deserves the same registration — in a schema registry, a dbt contract, whatever your stack uses — as any service dependency. The upstream team can't honor a dependency they can't see, and today most prompt-level field dependencies are invisible to every tool in the data platform.
Canary Assertions: Choosing Your Brittleness
Validation gates catch structural breakage. For semantic breakage — the gross-to-net revenue case — you need something with sharper teeth: canary assertions that make your LLM consumer exactly as brittle as your alerting requires.
The pattern: alongside production traffic, continuously run a small set of known-answer probes — inputs where the correct output is pinned. A synthetic customer record where the tier is platinum and the summary must say so. A fixed transaction batch whose extracted total must equal a known constant. These aren't evals in the model-quality sense; they're tripwires on the data path. If the pinned answer changes and the model didn't, the input contract moved.
A second variant is even cheaper: have the model itself surface contract anomalies as structured output. Add a field to your extraction schema — input_anomalies — and instruct the model to report missing expected fields, unrecognized enum values, or values whose units look inconsistent, rather than silently normalizing them. The model is genuinely good at noticing these things; by default it just has no channel to report them, so its coping is invisible. Give the coping a channel, alert when the anomaly rate for any field crosses a threshold, and you've converted the model from the thing that hides drift into a drift detector.
The word "brittle" is doing deliberate work here. Brittleness isn't the opposite of reliability — it's a dial. Traditional pipelines had it pinned at maximum (every anomaly is an outage). Naive LLM pipelines pin it at zero (no anomaly is anything). The right setting is a choice you make per field, per use case: billing-adjacent fields get hard gates and pages; cosmetic fields get counters and a weekly review. What's not acceptable is letting the model's temperament choose for you, because the model's temperament is always: cope, and don't mention it.
Tolerance Is a Budget, Not a Blessing
The LLM's ability to handle malformed input is real value — it's why these systems work on human-generated mess at all. The mistake is spending that tolerance on your own infrastructure. Tolerance spent absorbing a partner's inconsistent CSV exports is product capability. Tolerance spent absorbing your upstream team's unannounced schema change is deferred incident response, accruing interest.
So audit where the crashes used to be. Every place an LLM now consumes structured data that a parser used to consume, ask: what happens today if a required field disappears? If the honest answer is "the model would guess and nothing would alert," you've found a contract with no enforcement — and the enforcement won't come back on its own. Put the validation gate in front of inference, register the prompt's field dependencies as real dependencies, pin a handful of canary assertions on the paths that touch money or trust.
The old systems crashed because they couldn't cope. The new ones cope because they can't crash. Both defaults are wrong. The teams that get this right are the ones who notice that the crash was never the failure — it was the fire alarm — and who take the trouble to install a new alarm before the smoke has somewhere to hide.
- https://en.wikipedia.org/wiki/Robustness_principle
- https://cacm.acm.org/practice/the-robustness-principle-reconsidered/
- https://www.acceldata.io/blog/schema-drift
- https://www.conduktor.io/glossary/data-drift-in-streaming
- https://www.traceloop.com/blog/catching-silent-llm-degradation-how-an-llm-reliability-platform-addresses-model-and-data-drift
- https://www.siffletdata.com/blog/data-contracts-dont-work
- https://blog.pmunhoz.com/dbt/dbt-contracts-schema-enforcement-guide
