The seam you didn't type: killing the assumed-contract bug

There's a bug I've shipped more times than I'd like to admit, and it's never quite the same bug twice. A provider's API returns a field I read as a string; one day, for one account, it comes back null. A webhook payload grows an enum value nobody announced. A list that was always populated arrives empty. Each incident looks unique in the postmortem. They're all the same bug: I trusted the shape of data that crossed a boundary I don't control.

Every value that crosses a boundary you don't own is a rumor until you've parsed it.

Call it the assumed-contract bug. It isn't a typo and it isn't a logic error — it's a whole category, and the good news is you can design it out rather than swat it one incident at a time.

The shape is a rumor, not a contract

A provider's docs describe intent, not a guarantee. In practice a field is optional even though the page didn't say so. An enum grows. A number arrives as a quoted string. A null shows up where you expected a value. Pagination stops one page early. None of this is the provider misbehaving — you signed up for their format, and formats drift. What broke is that your code assumed a frozen contract that was never on offer.

Parse at the boundary, once

Give every external seam exactly one typed gateway. The raw dict off the wire gets to live for the length of a single function — the one that turns it into your own domain type. Downstream, nothing touches the raw response ever again. A missing field becomes a decision at the edge — a default, a rejection, a logged anomaly — instead of an AttributeError three layers deep in business logic, where you have neither the context nor the right to decide what it means. Pydantic, a hand-written parser, whatever you like; the point is the boundary, not the library.

Model the cases you'd rather ignore

The null. The empty list. The enum value you've never seen. The field that's a string on Tuesdays and a number on Fridays. Each one is a branch you write on purpose. Make Unknown a first-class member of your enum, not an exception. When the provider ships a status you've never heard of, the correct behavior is nearly always "treat it as unknown and keep going," not "throw, crash the consumer, and wedge the queue behind one weird record."

The test that fails on the old code

This is the step everyone skips. Write the test from a real malformed or edge payload — and then prove it fails against the code as it was before your fix. If the new test passes on both the old and the new implementation, you didn't test the fix; you tested something else and got lucky. The failing-first test is the only evidence the invariant is real. Once it's green on the new code, it stands guard against the day someone innocently "simplifies" the parser.

Why your tests were green the whole time

Here's the cruel version: everything passed, and it still broke in production. That happens because the mock returned the exact shape your code assumed. The mock and the code shared the same wrong belief about the provider, so of course they agreed. The fix isn't more mocks — it's grounding the seam in reality once. Capture an actual response, warts and all, and type against that. A single recorded real payload is worth a hundred hand-authored fixtures that all politely confirm your assumptions.

The boring truth

There's no cleverness in any of this. A parser at every boundary. A branch for every case you'd rather pretend can't happen. A test built from a payload you actually saw on the wire. It's unglamorous insurance against the one bug that's guaranteed to page you at a bad hour, over a schema change you didn't make and couldn't have prevented. A seam is where two systems meet — and where they meet is precisely where nobody owns the contract. So you write it down yourself.

← writing