---
id: 47
title: "Govern the contract between teams, not the code inside them"
status: current
kind: note
date: 2026-06-29
authors:
  - "Theo Zourzouvillys"
tags: [architecture, platform, api, culture, leadership]
references:
  - id: conway
    title: "Melvin E. Conway — How Do Committees Invent? (1968)"
    url: https://www.melconway.com/Home/Committees_Paper.html
    abstract: "The original paper behind 'Conway's law': any organisation that designs a system will produce a design whose structure is a copy of the organisation's communication structure. The team boundaries you draw become the interfaces in your system whether you intend them to or not."
  - id: team-topologies
    title: "Team Topologies — Skelton & Pais (2019)"
    url: https://teamtopologies.com/
    abstract: "Defines four team types and three interaction modes, and argues a platform should be run as an internal product — a 'Thinnest Viable Platform' that stream-aligned teams consume self-service to reduce cognitive load. The platform earns adoption by being good, not by mandate."
  - id: bezos-mandate
    title: "The Amazon API mandate (recounted in Steve Yegge's 'Platforms Rant', 2011)"
    url: https://gist.github.com/chitchcock/1281611
    abstract: "Amazon's ~2002 directive: all teams expose data and functionality through service interfaces only, with no back-door access; teams may build whatever they like behind the interface. The interface is the contract; everything behind it is the team's own business."
summary: "Teams own services end to end; one team owns the gateway that dispatches to them. Govern exactly one thing centrally — the contract at the boundary (schema, identity, errors, idempotency) — and enforce it at runtime. Don't mandate libraries; ship them as an opt-in blueprint."
supersedes: null
superseded_by: null
aliases: []
---

## TL;DR

**Draw the org so each team owns its services end to end, and put one team in front as the gateway
that dispatches to the rest. Then govern exactly _one_ thing centrally: the contract at the
boundary** — the schema, the identity model, the error and idempotency semantics, the quotas, the
versioning and deprecation rules. That contract is the API of your organisation.

**Do not mandate the libraries, frameworks, or processes a team uses to satisfy it.** Ship a
reference implementation — an SDK, a service template, a "paved road" — as an *opt-in blueprint*, not
a fence. A team may adopt it wholesale, fork it, or reimplement it in another language; you don't
care, because the gateway **enforces the contract at runtime**, so you never need to enforce the code
behind it. Verify behaviour at the boundary, not the provenance of what produced it.

That single move — central contract, sovereign internals — is what lets every team refactor, rewrite
([ZFN-23](/zfn/23-iterate-and-rewrite-implementations/)), re-language, and ship on its own cadence
without asking anyone's permission. The interface is the unit of governance; everything else is local.

## Context

This is Conway's law ([How Do Committees Invent?](ref:conway)) used on purpose instead of suffered by
accident. The boundary between two teams *becomes* an interface in your system whether you design it
or not — so design it, and put your governance there. The architecture you want falls out of the org
you draw.

The common mistake is governing the wrong layer. A platform team mandates "everyone uses our HTTP
client, our logging library, our framework, our deploy pipeline," believing it's manufacturing
consistency. What it's actually manufacturing is **synchronous coupling dressed up as a standard**.
Every team now upgrades on the platform team's cadence; a bug or CVE in the shared library freezes the
whole org until one team cuts a patch and everyone redeploys in lockstep; a team that wants a
different language is locked out; and the platform team becomes a bottleneck sitting in everyone's
critical path. You set out to build independent teams and you built a distributed monolith with extra
steps and worse latency.

Meanwhile the thing that actually matters — the contract at the boundary — is too often left vague,
"specified" by reading the other team's source and guessing. So you end up with the worst of both
worlds: rigid, mandated *implementations* and loose, undocumented *interfaces*. It should be the exact
opposite. Pin the interface hard; leave the implementation free.

A gateway makes this tractable because it's a real runtime chokepoint — the one place every request
already passes through. It's the natural home for the things that genuinely must be uniform (authn,
identity propagation, quotas per [ZFN-18](/zfn/18-enforce-quotas-at-ingress/), schema validation,
telemetry conventions) precisely *because* you can enforce them there instead of trusting that
everyone imported the right helper. It's [ZFN-34](/zfn/34-resource-free-bouncer-account/)'s "single
audited gateway" idea pointed inward, at your own teams.

## Recommendation

**Centralise the contract; decentralise everything behind it. Make the gateway enforce the contract,
and make the blueprint optional.**

- **Teams own their services end to end** — design, build, run, on-call
  ([ZFN-31](/zfn/31-own-your-components/)). Ownership includes the freedom to choose how, which is the
  half people forget.

- **One team owns the gateway, and its product is the contract and the dispatch fabric — not the
  services.** This is a platform-owned capability in the sense of
  [ZFN-5](/zfn/5-platform-workload-identity-service/): a thing the org consumes self-service, run as a
  product with versions, docs, and a support story. The gateway team's customers are the other teams.

- **Write the contract down precisely, because vagueness re-couples everyone.** Schema-first, with
  generated clients ([ZFN-14](/zfn/14-schema-first-apis-generate-clients/)); standard protocols rather
  than bespoke ones ([ZFN-30](/zfn/30-use-standards-dont-reinvent/)); explicit error semantics and
  idempotency, with read-only and idempotent operations annotated
  ([ZFN-19](/zfn/19-annotate-readonly-idempotent-endpoints/)); identity propagation rules; quotas and
  flow-control behaviour ([ZFN-13](/zfn/13-load-shedding-and-flow-control/),
  [ZFN-18](/zfn/18-enforce-quotas-at-ingress/)). If it isn't in the contract, it isn't guaranteed — and
  if a team relies on something *not* in the contract, that's the bug.

- **Enforce the contract at the gateway, at runtime.** This is the keystone. You check that an endpoint
  *honours* the contract — validates against the schema, returns the agreed errors, behaves
  idempotently — not that it was built with your code. Idempotency is a property of the endpoint, not
  of which library produced it. Once enforcement lives at the boundary, mandating the implementation
  becomes not just unnecessary but actively counterproductive. This is the modern echo of the
  [Amazon API mandate](ref:bezos-mandate): interface-only, no back doors, build whatever you like
  behind it.

- **Provide the blueprint as a paved road, not a law.** Ship a reference SDK and a service template
  that satisfy the contract out of the box, handle the boring correctness (retries, idempotency keys,
  auth, telemetry), and make the right thing the easy thing. Then let teams take it or leave it —
  that's a [Thinnest Viable Platform](ref:team-topologies) that earns adoption by being good. If your
  blueprint is genuinely better than what a team would write, most teams will use it, and the few who
  don't usually have a real reason (a different language, a latency budget, a domain they understand
  more deeply). A blueprint is a gift; a mandate is a leash.

- **Keep the gateway thin — defend this with your life.** It routes, authenticates, propagates
  identity, meters, validates schema, sheds load, and observes. It does **not** hold business logic.
  The moment cross-team logic starts accreting in the gateway, you've quietly recentralised: it becomes
  the scariest deploy in the building, every team is coupled to it again, and you're back to the
  monolith you were trying to avoid. Boundary concerns only — keep the control-plane and data-plane
  split honest ([ZFN-16](/zfn/16-separate-data-plane-control-plane/)).

Identity is the sharp, concrete example of all of this. When the gateway dispatches a request
downstream, it must **forward the caller's identity** to the service — delegate, never impersonate
([ZFN-38](/zfn/38-agents-are-principals/)) — and must never collapse every request into an anonymous
"gateway" system actor ([ZFN-40](/zfn/40-no-anonymous-system-actor/)). That's a *contract* rule, and
the gateway is exactly where you enforce it. How each downstream service then consumes the propagated
principal is entirely its own business. Central rule, sovereign implementation — the whole pattern in
one example.

> [!aside] From the field
>
> The failure mode that converted me wasn't a gateway — it was a mandated shared library. One
> "blessed" client, imported everywhere, *required*. It was good code. Then a vulnerability landed in
> one of its transitive dependencies, and because every service was pinned to it, the entire estate
> was exposed at once and *nothing* could ship a fix until the platform team cut a release and every
> team redeployed in lockstep. A mandate had turned dozens of independent services into a single fate-
> sharing unit — the exact opposite of why we'd split them up. The contract those services spoke at the
> boundary hadn't changed at all; we'd coupled ourselves on the *implementation* for no interface
> reason. After that I stopped mandating libraries and started mandating contracts. Same library,
> offered as a default instead of a law, would have let each team patch and ship on its own clock.

## Consequences

**Easier:**

- Teams ship on their own cadence and rewrite their internals freely
  ([ZFN-23](/zfn/23-iterate-and-rewrite-implementations/)) — adopt a new language, swap a datastore,
  re-architect — without a cross-team migration, because nothing outside depends on how they did it.
- The gateway team scales, because it owns a contract and a thin fabric rather than sitting in the
  release path of every service. Its blast radius and its workload are bounded by the boundary.
- You get uniform *external* behaviour — auth, quotas, identity propagation, telemetry — without
  uniform *internals*. The things that must be consistent are, and the things that needn't be aren't.
- Onboarding has an obvious on-ramp: the paved road gets a team to a conformant service fast, and
  opting off it later is cheap because the contract, not the code, is what actually holds.

**Harder:**

- You have to *actually specify* the contract — rigorously, including the error and identity and
  idempotency semantics people like to leave implicit. A vague contract leaks implementation details
  and silently re-couples teams; precision is now real, ongoing work.
- You need contract and conformance testing at the boundary instead of leaning on a shared library's
  correctness. The gateway-enforced checks and a conformance suite are the price of not mandating code.
- The blueprint has to be genuinely excellent and genuinely maintained, or teams route around it and
  you get *worse* fragmentation than a mandate would have. An optional paved road only works if it's
  the path of least resistance.
- You accept duplication: several teams each re-implement bits of the paved road in their own stack.
  That redundancy is the deliberate price of decoupling — pay it on purpose, don't let it shame you
  back into a mandate.

**New obligations:**

- Run the contract as a versioned product: documented, with a deprecation policy, sunset timelines,
  and a clear owner for breaking changes — recorded as decisions
  ([ZFN-1](/zfn/1-keep-engineering-decision-records/)) so the *why* survives. The contract is now a
  first-class artefact with a lifecycle, not a wiki page.
- Hold the line on a thin gateway. "Just put this one bit of logic in the gateway, it's easier" is how
  the distributed monolith grows back. Every addition to the gateway is a new thing every team is
  coupled to; treat it as such.
- Treat the blueprint as a maintained product with its own roadmap, not a one-time scaffold you bless
  and abandon. A paved road that rots becomes a dirt track everyone drives around.

## References

- [Conway — How Do Committees Invent? (1968)](https://www.melconway.com/Home/Committees_Paper.html) —
  why team boundaries become system interfaces; the case for governing them deliberately.
- [Team Topologies — Skelton & Pais](https://teamtopologies.com/) — platform-as-product and the
  Thinnest Viable Platform; a blueprint earns adoption rather than compelling it.
- [The Amazon API mandate](https://gist.github.com/chitchcock/1281611) — interface-only, no back
  doors, build whatever you like behind it; this note is that mandate plus "and here's a paved road."
- [ZFN-5](/zfn/5-platform-workload-identity-service/) — the gateway as a platform-owned, self-service
  capability.
- [ZFN-34](/zfn/34-resource-free-bouncer-account/) — the single-audited-gateway pattern, here pointed
  inward at your own teams.
- [ZFN-14](/zfn/14-schema-first-apis-generate-clients/) and
  [ZFN-30](/zfn/30-use-standards-dont-reinvent/) — the contract is a schema, and a standard one where
  possible.
- [ZFN-18](/zfn/18-enforce-quotas-at-ingress/), [ZFN-19](/zfn/19-annotate-readonly-idempotent-endpoints/),
  [ZFN-13](/zfn/13-load-shedding-and-flow-control/) — boundary behaviours the gateway enforces.
- [ZFN-38](/zfn/38-agents-are-principals/) and [ZFN-40](/zfn/40-no-anonymous-system-actor/) — propagate
  identity across the dispatch; don't impersonate, don't go anonymous.
- [ZFN-23](/zfn/23-iterate-and-rewrite-implementations/) and [ZFN-31](/zfn/31-own-your-components/) —
  what independent teams get to *do* once only the contract is fixed: own and rewrite their internals.
- [ZFN-16](/zfn/16-separate-data-plane-control-plane/) — keep the gateway a thin control/dispatch layer,
  not a place business logic accretes.

## Changelog

- **2026-06-29**: First published as a Field Note.
