Provider contract verification passes on every build, yet a provider release still broke a live consumer. How does a stale contract produce that false confidence?
answer
- Green is a statement about a file
- Ask how old that file is
- Which versions were actually compared
- Retired consumers still pass
- Absence of a contract fires no alarm
basics
~20 sVerification replays the contract that was last published, not the consumer's current code. If the consumer changed without re-recording, or the provider verified a version nobody runs, the build proves compatibility with expectations that no longer exist.
solid answer
~50 sA green verification is only as current as three things: the contract file, the versions it is verified against, and the seeding behind each provider state. Staleness enters when a consumer changes its parsing but its pipeline does not republish; when the provider verifies whatever contract is tagged latest while production runs an older consumer, or the reverse; when contracts for retired consumers keep passing and mask that a live one has none; and when a state seeds shapes production never produces. The fixes are procedural more than technical: publish the contract on every consumer build, tagged with the consumer's version and where that version is deployed; record verification results against specific provider and consumer versions; gate the provider's release on "has this exact build verified every consumer version deployed there" rather than on a green suite; and alert on contracts with no recent publication.
code
pseudocode · 19 linesdeployGate(provider = "meter-reading-feed",
providerVersion = "c1f9e40",
environment = "production"):
consumers = repository.consumersDeployedTo(environment)
if consumers is empty:
return BLOCK("no deployed consumers recorded for " + environment)
for c in consumers:
contract = repository.contractFor(c.name, c.deployedVersion)
if contract is missing:
return BLOCK(c.name + " " + c.deployedVersion + " published no contract")
result = repository.verificationResult(contract, providerVersion)
if result is missing or result.failed:
return BLOCK("no passing verification for " + c.name +
" " + c.deployedVersion)
return ALLOWgo deeper
Take away one idea: a passing verification describes the contract file that was supplied, so a file that was not republished after the consumer changed proves nothing about the consumer running today.
Be able to name where staleness enters — publication cadence, version pairing, retired consumers, drifting state data — and say which pipeline step you would move to close each one.
Show that you would gate the release rather than the build: for this provider build, is there a passing verification for every consumer version deployed to the target environment, with a missing result treated as a failure?
Own the lifecycle across teams: who is accountable for publication, how unprotected integrations are surfaced, and how much release coupling the organisation is willing to accept in exchange for the guarantee.
## The claim a green run actually makes Verification proves one narrow statement: *this build of the provider satisfies the expectations in the contract files it was given*. Every word carries risk. If the files are old, the statement is about an old consumer. If the files came from a consumer version that nobody is running, the statement is about code in nobody's production. If the provider build that verified is not the build being released, the statement is about a different artefact. A pipeline can be green on all of them and still ship a break. ## Five ways staleness gets in **1. The contract was never republished.** The consumer starts reading a field it previously ignored, or begins sending a query parameter. Its own tests pass against its stand-in, but publication is wired to a nightly job, a manual step, or only the main branch. The provider keeps verifying the older file, which never mentions the new field, so removing that field verifies green and breaks the consumer on deploy. **2. The wrong versions were compared.** Verification runs against "the latest contract", while production runs a consumer version two releases behind whose expectations were stricter. Or the reverse: the newest contract is verified, then the provider ships a *different* commit that was never verified. Without recording which provider version verified which consumer version, the matrix that matters is not being consulted at all. **3. Dead consumers keep the suite green.** A retired consumer's contract stays in the repository and keeps passing, adding runtime and, worse, a comforting count of verified consumers. Meanwhile a newly built consumer that publishes nothing is entirely absent. Nobody notices, because the dashboard shows green contracts, not missing ones. **4. The recording drifted from the consumer's behaviour.** Contracts hand-edited to "fix" a failure, or generated from a consumer test that stubs the client rather than the transport, describe intentions rather than usage. The provider then honours a document the consumer's code does not follow. **5. Provider states seed shapes production never yields.** Consider a four-person team owning a smart-meter reading feed. The state handler seeds a reading whose `takenAt` is taken from the verification machine's clock, while the consumer's expectation matches a timestamp with an explicit offset. Runners in one region produce offsets that satisfy the matcher; a runner whose clock skews across a day boundary produces one that does not. The result is a verification that is green or red according to where it ran — a clock-skew artefact — and once someone loosens the matcher to stop the noise, the contract has quietly stopped asserting the field at all. ## What to change **Publish on every consumer build.** Contract publication belongs in the consumer's pipeline next to its unit tests, tagged with the consumer's exact version and its branch. A contract that is only published from a release job is stale for the whole time a change is in flight. **Record results per version pair.** A verification result is a fact about (provider version, consumer version), not about a day. Storing it that way is what makes the next item possible. **Gate the deploy, not the build.** Before releasing a provider build to an environment, ask the repository whether *that* build has a passing verification for *every consumer version currently deployed there*. A missing result is a block, exactly like a failure. This is the single change that converts contract tests from documentation into a release control, and it is the answer interviewers are usually listening for. **Verify both the deployed set and the heads.** Deployed consumer versions protect production; the consumers' main branch heads give the provider early warning about what is coming. **Make silence visible.** Alert on contracts with no publication in the last N days, on consumers deployed with no contract at all, and on states whose handlers have not run. Staleness is the absence of a signal, and absence never fires an alarm on its own. **Seed deterministically and through the application.** Freeze the clock in state setup, avoid ambient locale and randomness, and write through the same path the application uses so the seeded shape is one the system can actually produce. ## How to talk about it The strong answer separates *the mechanism worked and told you the truth about an old world* from *the mechanism is broken*. Nothing here is a defect in contract verification; each failure is a gap between the artefact's age and the world's. The interviewer wants to hear the lifecycle owned end to end: recorded on every consumer commit, published with an identity, verified against the versions that exist, consulted at deploy, and audited for silence.
- How would you detect a consumer that is deployed but has published no contract at all?Cross the deployment record against the contract repository rather than looking at the suite. Every release records which consumer version went where; the repository knows which consumer versions have published files. Anything in the first set and not the second is an unprotected integration. Making that comparison a blocking check on the provider's own deploy is what stops the gap being tolerated indefinitely.
- A brand-new consumer expectation fails the provider's build before the provider has implemented it. How do you keep that from blocking unrelated releases?Mark newly published expectations as not-yet-required until they have been verified once, so they are reported without failing the provider's build, and require them from then on. That preserves the signal — the provider team can see what is coming — without giving any consumer the power to redden an unrelated release the moment it publishes.
- Should a failing contract for a retired consumer block the provider's release?No, and the fix is to remove the consumer's deployment record and its contracts rather than to ignore the failure. If the release gate asks only about consumer versions currently deployed, a retired consumer stops mattering automatically. Leaving dead contracts in place teaches the team to skim past red, which is a far more expensive habit than the deletion.
A stale contract is a spare key cut for a lock that has since been changed: it still turns cleanly in the test rig, because the test rig kept the old lock.
saying these in an interview costs you the question
- Treats a green suite as proof both live sides agree
- Verifies only the newest contract, ignoring deployed versions
- Loosens a matcher to silence an environment-dependent failure
- Keeps retired consumers' contracts as evidence of coverage
- Publishes contracts only from a release job, not every build
- Blames the technique instead of the contract's lifecycle