Daniel's been turning over a word this week — contract. It shows up everywhere in development. Client-API contracts, TypeScript contracts, data contracts. And he says he's never once gotten a straight answer about what it actually means. So he did what he does. He went back to the source. Bertrand Meyer, Eiffel, design by contract, nineteen eighty-six. And then he came forward again asking whether the word still means anything precise, or whether it's just a comfortable metaphor for the agreed shape of something. He wants a test — what makes a real contract — and he wants us to run five common usages through it. A TypeScript interface describing an API response. Schema-first artifacts like OpenAPI and protobuf. The database schema. Consumer-driven contract testing. And data contracts in data engineering. Plus two claims he wants pressure-tested: a contract only one party can see is not a contract, and a contract without a change process is only a snapshot.
So we went looking for a straight answer. And the word has drifted so far from its roots that it might mean almost nothing. Or something very specific, depending on which room you're standing in.
Let's start where the word started. French computer scientist, language called Eiffel, nineteen eighty-six.
Bertrand Meyer coined design by contract while designing Eiffel, and the original meaning was rigorous in a way that would surprise most people who use the word today. The metaphor was borrowed from business. A client and a supplier agree on mutual obligations and benefits. The supplier must deliver a product, and is entitled to expect payment. The client must pay, and is entitled to receive the product. In software terms, a method's precondition is the client's obligation and the supplier's benefit. The postcondition is the supplier's obligation and the client's benefit. And there's a class invariant that has to hold on entry and be guaranteed on exit.
So it's not a description of a shape. It's a symmetry of obligation.
Right, and here's the part that gets lost. These contracts were runtime-enforced. Eiffel has keywords — require for preconditions, ensure for postconditions, invariant for class invariants. The language checks them as assertions while the program runs, typically in debug mode, and if a precondition is broken, the program fails hard. Meyer called it offensive programming — the opposite of defensive programming. Under design by contract, the supplier assumes the precondition holds. It does not check, does not handle the broken case gracefully. It fails immediately and loudly.
Which is almost the opposite of how most frontend code is written now. Everything's defensive. Optional chaining everywhere, fallback values, graceful degradation.
And that's the drift. The original contract was a runtime guarantee between two parties in the same process, enforced by the language itself. Semantically equivalent to a Hoare triple, by the way. Three questions: what does it expect, what does it guarantee, what does it maintain. Today, when a frontend developer says contract, they almost always mean a static description of a data shape. A TypeScript interface. A schema. Something with zero runtime enforcement. The exact opposite of what made the original concept powerful.
So the word went from a guarantee to a hope.
That's not a bad summary. And Daniel's question is whether we can recover the precision. So let's build a test.
Before we do, one more thing about the original. Subclasses could weaken preconditions and strengthen postconditions and invariants. That's behavioral subtyping, roughly. Which means the contract had rules about how it could change under inheritance. Even in nineteen eighty-six, the change process was part of the definition.
That's going to matter later. Now the test. Daniel proposed five criteria. One, a specification that exists as an artifact independent of either side. Two, two parties actually bound by it. Three, some enforcement mechanism. Four, a story for versioning and breaking changes. Five, a defined consequence for violation.
I like the test. It's almost a legal definition. Offer, acceptance, consideration, breach, remedy.
That's the metaphor doing real work. A contract in law binds two parties to an exchange and says what happens when one side fails. If your software contract doesn't answer those questions, it's not a contract. It's a note.
So run the cases. Start with the loosest. TypeScript interface describing an API response.
This fails almost immediately. The interface lives in the frontend repo. The server has never seen it. TypeScript erases types at runtime, so nothing checks that the JSON actually matches. Loiane Groner put it bluntly in a piece this summer about Angular and Spring Boot contract testing. The TypeScript interface is not a contract. It is a comment that happens to be written in TypeScript.
That's going on a shirt.
She also called it a hopeful transcription of a JSON shape that the backend is free to change without telling anyone. And there's a good example from a JSON-to-TypeScript generator that got one of four fields right on its own. The id was correct. The coupon, the items, the status were all wrong. Because a generator can only see the sample it was given. It cannot see nulls, empty arrays, unions, the things that only show up across many responses.
So the interface fails the test on almost every criterion. No independent artifact — it lives in one repo. No second party — the server is unaware. No enforcement — types vanish at runtime. No versioning story. No consequence for violation, because from the server's perspective there's nothing to violate.
It's an assertion of belief, not a guarantee. iotools put it as, an interface describes what you hope arrives. Runtime validation describes what did arrive. You need both, and only one of them survives to runtime.
And the interface is the one that doesn't survive.
Correct. So case one fails outright. It's not a contract. It's a wish.
Case two. Schema-first artifacts. OpenAPI, protobuf, gRPC, GraphQL, tRPC.
This is the closest to the original sense, but it's partial. When you have a canonical spec that both sides derive from — an OpenAPI document, a protobuf definition — you've got the independent artifact. You've got two parties, at least in principle. You've got codegen on both sides, so there's some enforcement at build time. But PactFlow has been making the argument since at least twenty twenty-four that schemas alone still aren't contracts. Schemas are abstract, contracts are concrete.
What's the distinction?
A schema defines the data types and inputs and outputs a single system supports at a point in time. It doesn't capture HTTP-level semantics — the verb, the path, the status codes, the headers. It's ambiguous about which inputs produce which status codes. And critically, it doesn't track which fields each consumer actually uses. So when you codegen from a schema, you have to assume every consumer uses the entire surface area. That makes evolution harder, because any change to the schema looks like it might break every consumer.
Tim Jones, the Pact maintainer, said a spec is not a contract. Most people who use OpenAPI without Pact also pair it with a Postman collection of request and response examples. That pairing of spec plus examples is a contract.
That's the key. A schema is necessary but not sufficient. You need concrete examples of actual interactions, and you need something that checks those interactions still work. A spec says what should happen in the abstract. A contract says what did happen, verified, between these two systems.
tRPC is an interesting variant. Shared inference from a single codebase. The types are inferred from the server implementation and shared across the boundary automatically.
And that's elegant, but it couples both sides to one implementation. There's no independent artifact — the codebase is the artifact. There's no enforcement mechanism outside the type checker, and there's no versioning story, because both sides move together. It's a contract in the sense that a pair of conjoined twins have an agreement.
That's vivid and I wish it weren't.
So case two passes some criteria — independent artifact, two parties, some enforcement — but fails on versioning and on concrete verification. It's a partial pass. A schema is a contract candidate, not a contract.
Case three. The database.
This is the one that surprises people. The database schema is often the only layer with real runtime enforcement, and it's the closest everyday analog to Meyer's original design by contract. The database enforces not null, foreign keys, uniqueness, check constraints. It fails hard on violation. You try to insert a null into a not null column, the database rejects it. You try to delete a row that's referenced by a foreign key, the database rejects it. That's offensive programming. That's the fail-hard philosophy, running in production, every day.
And yet almost nobody calls it a contract.
Right. People say schema, or migrations, or constraints. But run it through the test. Independent artifact — the schema lives in migration files, separate from either side, or in the database itself. Two parties — every application that touches the database is bound by it. Enforcement — the database engine itself. Versioning — migrations have a linear history, and breaking changes require explicit steps. Consequence for violation — the transaction fails. It passes all five criteria.
And the ORM-generated types are derived from the schema, not authoritative over it. The database is the source of truth. The types are a projection.
Which is the correct direction. The database says what is allowed. The types describe what the database said. If they disagree, the database wins.
So the most rigorous contract in most stacks is the one nobody calls a contract.
And that's the irony Daniel's poking at. The word has drifted so far that the thing which best fits the original definition has lost the word, and the thing which least fits it — the TypeScript interface — has claimed it.
Case four. Consumer-driven contract testing. Pact, Spring Cloud Contract.
This is the strongest modern heir to Meyer, and it's also the most philosophically interesting. Here's how it works. The consumer writes a test that sets out its assumptions and needs from the provider. Pact writes those interactions into a contract file, a JSON document. The producer then replays that contract file against the real service. If the producer can't satisfy the consumer's expectations, the producer's build fails.
So the contract is a literal negotiated, executable artifact.
Generated from a real interaction, then replayed against the real producer. Run it through the test. Independent artifact — the pact file, stored in a broker. Two parties — consumer and producer, both bound. Enforcement — the build fails. Versioning — the Pact Broker tracks versions, and there's a can-i-deploy gate that stops deployment if the other side hasn't verified the latest contract. Consequence — the team that made the breaking change is the team that sees the failure. It passes all five.
And the key property is that the consumer only asserts what it uses. If the backend adds a new field the consumer doesn't care about, the contract doesn't break. Compatibility, not identity.
That's the crucial insight from Loiane's piece. The contract is the minimum the consumer depends on, so the producer stays free to add anything without breaking it. A contract doesn't describe the full data shape. It describes the minimum expectations. Additive changes are fine. Removing or renaming a field the consumer uses is breaking.
That reframes a lot of arguments about breaking changes. Most teams treat the full schema as the contract, so any change to the schema looks like a potential break. If the contract is the minimum the consumer depends on, most schema changes are invisible.
And that's what the can-i-deploy gate enforces. Before you deploy the producer, it checks whether the consumer has verified the latest contract. Before you deploy the consumer, it checks whether the producer has verified. If not, the deploy is blocked. The change process is built into the tooling.
Case five. Data contracts in data engineering.
Newer and different. The Data Contract Specification — first public release September twenty twenty-three, currently at version one point two point one — defines a data contract as a document covering structure, format, semantics, quality, and terms of use for exchanging data between a provider and their consumers. It includes schema, quality checks, SLAs like availability and retention and freshness, terms of use, and server information. And it uses semantic versioning, with a command-line tool that validates the model against actual data.
So it's an API contract, but for datasets.
That's literally the framing. Think of an API, but for data. The data engineering world looked at what software engineering did with API contracts a decade ago and said, we need that. And in some ways they've been more rigorous. They include SLAs. They include quality rules. They include breaking-change detection in the versioning. Confluent's Schema Registry now offers data contracts with quality validation rules and migration rules.
And the motivation is concrete. Gartner's figure is that poor data quality costs organizations an average of twelve point nine million dollars a year, most of it from uncoordinated producer-consumer changes.
That's the number that gets budget. So case five passes the test, with caveats. The enforcement is in CI, not at runtime in the data pipeline itself. But the artifact is independent, the parties are bound, the versioning story is explicit, and the consequence is a failed pipeline or a blocked deploy.
So the scoreboard. Case one fails outright. Case two is partial — schemas are necessary but not sufficient. Case three passes, and nobody calls it a contract. Case four passes, and it's the closest heir to Meyer. Case five passes, and it's a deliberate re-import of API discipline into data.
And the test is what separates genuine contracts from shapes written down. If it doesn't bind two parties, if it isn't enforced, if there's no change process and no consequence, it's a shape. It's a note. It's a hope.
Now the two deeper issues. Visibility and change.
Visibility first. A contract only one party can see is not a contract. This is why the TypeScript interface fails so completely. The server can change the shape without telling anyone, because from the server's perspective there's no one to tell. There's no second party bound. No enforcement. No consequence. Loiane's line again — it's an assertion of belief, not a guarantee.
And the failure mode is silent. The frontend doesn't find out until runtime, when a field is undefined and something crashes in production. Or worse, the field is there but the type is wrong, and the bug is subtle.
That's the iotools point. The interface describes what you hope arrives. Runtime validation describes what did arrive. Only one of them survives to runtime. If you don't have runtime validation at the boundary, and you don't have a second party who's aware of the contract, you have nothing. You have a comment.
So visibility is binary. Either both parties see the artifact, or it's not a contract.
The artifact has to be independent. It can't live in one party's repo. It has to be something both parties can point to and say, that's the agreement. The pact file in the broker. The migration file in the shared repo. The data contract document in the catalog.
Change process second. A contract without a change process is only a snapshot.
PactFlow's argument. If you want the broader guarantees and benefits of a contract, you need a reliable mechanism to introduce evolution, collaboration, and conversations into the process. Without versioning and a breaking-change policy, a contract is a static document that will drift. It describes what was true at one moment, and then reality moves on.
The drift is invisible until something breaks.
Right. The contract doesn't rot loudly. It rots quietly. The backend adds a field, the frontend doesn't notice. The backend changes a field from string to number, the frontend finds out at runtime. Without a change process, the contract is a historical document, not a living agreement.
What makes a change breaking versus additive?
This is where consumer-driven testing gives the clearest answer. Adding a field the consumer doesn't use is not breaking. Removing or renaming a field the consumer uses is breaking. The contract is the minimum the consumer depends on. The producer is free to add anything without breaking it. Compatibility, not identity.
That's a useful distinction. Most teams treat the full schema as the contract, so any schema change looks like a potential break. If the contract is the minimum, most schema changes are invisible.
That's what makes evolution possible. If every additive change is a breaking change, you can never evolve. You're frozen. The minimum-contract model says, add whatever you want, just don't remove or rename what I depend on.
Who gets to change a contract unilaterally?
In consumer-driven testing, the consumer authors the contract. The producer must verify it can keep it. So the consumer can change its expectations — that's its right, it's declaring what it needs. But the producer that makes a breaking change is the one whose build fails. The team that made the change is the team that sees the failure.
Neither party can change the contract unilaterally without the other party noticing. The tooling enforces the notice.
The can-i-deploy gate is the enforcement. Before the producer deploys, it checks whether the consumer has verified the latest contract. Before the consumer deploys, it checks whether the producer has verified. If either side hasn't, the deploy is blocked. The change process is built into the pipeline.
In data contracts, the Data Contract Specification uses semantic versioning with explicit breaking-change detection. A major version bump means breaking. A minor bump means additive. The tooling checks.
The answer to who can change unilaterally is, in a genuine contract, nobody. Or rather, anyone can propose a change, but the change doesn't take effect until the other side has verified it. The contract is a shared artifact with a shared change process.
Now the philosophical inversion. This is the part I keep turning over.
Go.
In Meyer's design by contract, the supplier declares the preconditions. The method says, here's what I require from you, the client. The client must meet those preconditions or the method fails hard. The power sits with the supplier.
In Pact, the consumer authors the contract. The frontend says, here's what I need from you, the backend. The producer must verify it can keep those terms. The power sits with the consumer.
The party with the least power — the frontend, historically the one that had to adapt to whatever the backend did — now dictates terms. It's a complete inversion of who holds responsibility.
It's philosophically interesting because it mirrors what happened in software development generally. The frontend used to be the client in the old sense — the one who had to pay, to adapt, to meet the supplier's preconditions. Now the frontend is the customer, and the customer is always right.
Which is a better model, actually. The consumer knows what it needs. The producer knows what it can provide. The contract is where those two meet.
But it also creates a new failure pattern. If the consumer authors the contract and the producer just has to keep it, the producer can end up frozen. Every consumer's minimum expectations become a constraint. Add enough consumers, and the producer can't change anything without breaking someone.
That's the trade-off. The old model was flexible for the supplier and brutal for the client. The new model is flexible for the consumer and constraining for the producer.
That's why the change process matters so much. Without a process for evolving the contract, consumer-driven testing can become a straitjacket. With a process — versioning, can-i-deploy, negotiation — it's a living agreement.
There's also the commercial angle. PactFlow sells contract-testing tooling, so they have a stake in arguing schemas are insufficient. Schema-first advocates have a stake in arguing a canonical spec is the contract. The debate is partly commercial.
But it reflects a genuine technical question. Is an abstract spec or a concrete verified interaction the better contract? And the answer depends on your risk tolerance and your team structure. If you have a small team and a single consumer, a schema is probably enough. If you have many consumers and independent deploy cycles, you need the concrete verification.
The test isn't a purity test. It's a risk assessment. The more parties, the more independent the deploys, the more you need the full apparatus.
The word contract is doing real work when it forces you to ask those questions. Who's bound? What happens on violation? How do we change this? If the word doesn't force those questions, it's decoration.
Where does that leave the word itself? Retire it? Replace it with spec or agreement?
I don't think retire it. I think the metaphor is still useful precisely because it forces the questions. A spec describes. An agreement binds. A contract binds and says what happens on breach. If we reserve the word for the things that actually bind, we recover the precision.
If we call a TypeScript interface a contract, we're lying to ourselves about what we have.
We're telling ourselves a story about safety that doesn't exist. The interface feels like a guarantee. It isn't. It's a hope. And the difference matters the first time the backend changes a field and production crashes.
Next time you hear contract, ask the five questions. Is it independent? Are two parties bound? Is there enforcement? What's the change process? What happens on violation? If the answer is no, it's a shape written down.
A shape written down is still useful. It's documentation. It's a type. It's a schema. It's just not a contract.
Hilbert: Nineteen ninety-seven. I was a contract programmer for a fintech startup in Boston. We wrote Eiffel. The whole codebase, Eiffel. Trading system. Design by contract was mandatory. Every method had require and ensure clauses. The compiler checked them in debug mode. We shipped with assertions on. The CTO said turning them off was like removing the brakes from a car because you don't plan to stop.
That's a man who understood fail hard.
Hilbert: We had a contract meeting once. The CEO wanted to weaken a precondition on a settlement method. The CTO said no. They argued for two hours. The CTO stormed out. The precondition stayed. The CEO was right, by the way. The precondition was too strict. But the process worked. You couldn't change the contract without a fight.
That's the change process we were talking about. It's social before it's technical.
Hilbert: The word contract meant something else in that job too. We had a legal contract with our biggest client. Ninety-nine point nine nine percent uptime SLA. Penalty clause. Real money. One day the system went down. Bug in a postcondition. The assertion should have caught it, but it was a race condition, and the assertion fired after the damage was done. We lost the client. The legal contract had teeth. The code contract had teeth. The gap between them was the bug.
The metaphor was always about lawyers, not code.
Hilbert: That's what I've been thinking. Meyer borrowed the word from business. Client, supplier, obligation, benefit. But the reason the word has power is the legal sense. A contract says what happens when someone fails. Most software contracts don't say that. They just describe a shape.
The ones that do say it — the database constraint, the pact file, the data contract with an SLA — are the ones that earn the word.
Hilbert: I still have the Eiffel manual. Bertrand Meyer signed it. I got it at a conference in Santa Barbara. He wrote, fail hard, fail early, in the margin. The only thing in my entire career that ever made sense.
Of course he did.
Hilbert: I've been wondering whether the thing you described — the can-i-deploy gate — would let me check whether a client's system still meets the contract before I send an invoice. I have a client. The contract says they pay within thirty days. They haven't. I'd like to block their deploy until they do.
That's not what can-i-deploy does.
Hilbert: It blocks deploys. That's the part I need.
You want to hold a client's production system hostage over an unpaid invoice.
Hilbert: I want to enforce the contract. That's what you've been saying. Enforcement is what makes it real.
The tooling enforces the software contract. It doesn't enforce the legal contract. Those are different layers.
Hilbert: They shouldn't be. That's my point. The word drifted because we separated them. The legal contract has teeth but no code. The code contract has code but no teeth. The database is the only place they meet.
The database fails hard. The legal contract fails hard. The TypeScript interface fails soft. It's the only one of the three that calls itself a contract.
Hilbert: I'm going to look into whether Pact can be configured to check invoice status. Probably not. But it's the first tool I've heard of that thinks about contracts the way I do.
The open question. If the word is so diluted, should we retire it? Or is the metaphor still useful because it forces us to ask who's bound and what happens on violation?
I think the metaphor earns its keep. A spec describes. An agreement binds. A contract binds and says what happens on breach. If we reserve the word for the things that actually bind, we recover the precision. And the test is the tool. Five questions. Independent artifact, two parties, enforcement, change process, consequence. If the answer is no, it's a shape written down.
As data contracts and consumer-driven testing grow, the word may regain its rigor. But only if teams adopt the enforcement and versioning that make it real.
The misconception most people hold is that a TypeScript interface is a contract. It isn't. It's a one-sided assertion with no enforcement. The server is unaware of it. The real contracts in your stack are the database constraints, the pact files, the data contracts with SLAs. The ones with teeth.
Thanks to Hilbert Flumingtop for producing.
This has been My Weird Prompts. Email us at show at my weird prompts dot com.
We'll be back soon.