The GraphQL schema registry decision record: why the schema composition model you chose determines your breaking change detection surface and your client compatibility window
GraphQL schema registry decisions are made in three founding sessions that never document the operational consequences — the schema stitching session that picks field naming without a breaking change detection process, so that a field rename causes blank transaction dates in the mobile app for 11 days across 3,800 sessions; the federation migration session that does not specify the subgraph ownership contract, so that an extension subgraph asserts an unresolvable @key field and blocks all five teams from deploying for 3.5 days; and the schema registry setup session that sets a 7-day validation window without accounting for the mobile app's 6-to-8-week App Store review cycle, so that a field removal passes the operation check and breaks 14,200 iOS sessions for 6 days while an emergency app update goes through App Store review. What none of these sessions produce is the client operation registration requirement that defines the detection surface of the schema registry, the compatibility window specification that must be set to the release cycle of the slowest-releasing client in the system rather than the average, or the subgraph ownership contract that governs which teams can assert entity keys that the owning subgraph must be able to resolve.
A 38-person fintech SaaS migrated their REST API to GraphQL using Apollo Server's schema stitching pattern. Three backend teams each owned a portion of the schema: the accounts team owned user account fields and authentication data, the transactions team owned the financial transaction history and details, and the portfolios team owned investment position and performance data. The migration was completed over four months, and the teams were satisfied with the result — GraphQL's strongly typed schema and client-specified field selection had already reduced over-fetching in the mobile app by a measurable margin. The decision to use schema stitching was documented in the founding migration session as "GraphQL via Apollo Server, schema stitching across three service domains." No further operational specifications accompanied that decision.
One specification that did not appear in the founding session was a breaking change detection process. The working assumption shared by all three teams was that GraphQL is additive by default, and that breaking changes were obvious rather than subtle: removing a field entirely was a breaking change, adding a new field was not. This assumption was correct for the most obvious cases and wrong for the most common ones. Field renames — changing transactionDate to executedAt during a domain model refactor, for instance — are breaking changes that are invisible to GraphQL's type system because the type system enforces the presence of declared fields, not the continued presence of fields that used to exist and have been renamed. A field rename is implemented as adding a new field and removing the old one. Adding is safe. Removing is the breaking change.
Eight months after the GraphQL migration launched, the transactions team renamed the field transactionDate to executedAt as part of a broader naming consistency effort aligned with a new domain model. The change passed code review — the new field name was correctly typed, the resolver was updated to populate it from the same data source, and the TypeScript types in the service were updated throughout. The CI pipeline ran a suite of integration tests that queried the API using the new executedAt field and passed green. The deployment proceeded without incident. The mobile app, which had been querying transactionDate since the GraphQL migration, was not part of the CI test suite for the transactions service. Its queries were not run as part of the deploy gate. The mobile team was not notified of the rename.
GraphQL's non-nullable field handling meant that the missing field did not produce a request-level error. The type system returned null silently for the missing field rather than a request-level error — the response was technically valid according to the schema's nullability contract, because transactionDate was now simply a field that did not exist in the schema, and queries for nonexistent fields in schema stitching configurations returned null rather than raising a validation error. The mobile app rendered the transaction list with empty date fields without surfacing an exception. No error rates increased. No alerts fired. The bug manifested as a data quality issue rather than a broken API.
The root cause was identified 11 days later, after a customer support ticket described transaction dates appearing blank. The support ticket was routed through two queues before reaching the mobile engineering team, who identified the missing field in the query payload within an hour of receiving it. The field rename was immediately identifiable in the commit history. Across those 11 days, 3,800 mobile app sessions displayed blank transaction dates for every transaction in the transaction history. The transactions team reverted the rename within two hours of the root cause identification, adding a deprecation notice to the old field and running both fields in parallel until the mobile app could be updated to use the new name. The founding session documented "GraphQL via Apollo Server, schema stitching across three service domains." It did not specify a breaking change detection process, a client operation registration requirement, or a field deprecation workflow before removal — the three specifications whose absence made the silent null failure possible.
A 52-person SaaS platform migrated from schema stitching to Apollo Federation v2 to enable independent subgraph deployments. The motivation was team autonomy: under schema stitching, a single gateway process composed the schemas at startup, and a schema change in any team's service required a gateway restart before the new field was visible to clients. Federation's push-based composition model meant that each subgraph could publish a schema change to the schema registry and the gateway would pick up the composed schema without restarting. Five subgraphs were defined across the platform's service domains: users, billing, projects, notifications, and analytics. The User type was the central entity in the data model, referenced by all five subgraphs. It was defined with @key(fields: "id") in the users subgraph, and the billing and projects subgraphs extended it to add their respective fields.
The migration was planned and executed across two sessions focused on federation topology — which subgraphs to define, which entities each subgraph owned, and how the gateway's supergraph schema would be composed from the subgraph schemas. Neither session documented what "owned" meant in terms of governance for key fields. The first session established that the User type was "owned by the users subgraph." The second session specified the subgraph routing rules and the schema registry integration. Neither session produced a document that answered the following questions: which team must approve changes to the @key fields of any entity type, whether extension subgraphs could propose additional @key fields for entities they extended, and what approval process governed federation topology changes such as adding new entity keys or new subgraph extensions. These questions were not asked because they were not visible as questions — the team understood ownership intuitively as "the users subgraph is where the User type is defined," without working through the implication that ownership in a federated system is a governance contract, not just a code location.
Six months after the migration, the analytics subgraph team began building a new user-level analytics feature that needed to look up users by email address. The feature required resolving User entities from an email-address input rather than a user ID. The analytics team added @key(fields: "email") to the User entity in their subgraph extension and deployed the change to the analytics subgraph. The gateway's schema composition process failed at startup: the users subgraph's User entity definition declared @key(fields: "id") as its only resolvable key, but the analytics extension asserted that email could also be used as a resolution key. The gateway's composition process checked whether the owning subgraph's __resolveReference function could handle an {email} reference object — it could not, because the users subgraph's resolver was written to accept only {id} references. The composition error message was "Cannot merge conflicting @key directives for type User" — accurate but not immediately actionable for engineers who had not internalized the constraint that every @key field asserted by an extension subgraph must be resolvable by the owning subgraph's reference resolver, not just by the asserting subgraph.
The composition failure prevented the gateway from starting with the new composed schema. All five subgraph teams lost their ability to deploy during the 3.5 days of investigation — not because their subgraphs had errors, but because the gateway's composition check was a prerequisite for any subgraph deploy to take effect, and the composition check was failing for every attempted schema update until the conflicting @key directive was resolved. The investigation required three engineers across the users and analytics teams to identify that the error originated in the analytics subgraph's extension, understand why the @key conflict caused a full composition failure rather than a partial one, and decide on the correct resolution (add email as a supported key in the users subgraph's resolver before the analytics extension could assert it). The founding session documented "Apollo Federation v2, five subgraphs, User entity owned by users subgraph." It did not document what "owned" meant in terms of key field governance, the consequence of an extension subgraph asserting an entity key that the owning subgraph's reference resolver did not support, or the approval process that would have required the analytics team to coordinate with the users team before adding a new entity key to a type they extended.
A 35-person platform SaaS added Apollo Studio's schema registry with Rover CLI integration to their CI pipeline after a breaking change incident in which a field was removed without coordinating with a client team. The integration ran rover subgraph check on each schema change, blocking deploys that would break any registered client operation. The configuration set a --validation-period P7D, checking the proposed schema change against the last 7 days of operation data in Apollo Studio. The founding session that set up the registry documented "schema registry with operation checks, 7-day validation window." That documentation was accurate. What it did not document was the reasoning for the 7-day window, whether the window was sufficient for all clients in the system, or which clients were required to register their operations as a prerequisite for being protected by the check.
Three months after the registry was deployed, the platform's web client team integrated their client-side Apollo Client configuration with the rover client push workflow. On each web client build, the build pipeline extracted the operation manifests from the compiled JavaScript bundle and pushed them to the Apollo Studio operation registry. The mobile app team's CI pipeline predated the rover integration. The mobile builds ran a different set of checks, and the founding session that set up the registry had not specified that mobile builds must also register operations, had not defined which clients were required to complete registration, and had not added a gate to the mobile build pipeline that would fail the build if operations had not been registered. The mobile app was building and deploying to the App Store without its operations appearing in the registry. The schema registry had no record that the mobile app existed as a client.
A backend team responsible for the reporting domain deprecated and then removed the legacyReportFormat field 45 days after the web client had migrated away from it. The deprecation was announced in a Slack channel used by backend and web client engineers. The mobile team's Slack workspace was separate. The operation check ran against the last 7 days of registered operations. No registered operation in the previous 7 days used legacyReportFormat. The check passed. The field was removed in the next deploy. The iOS app, which releases through a 4-to-6-week App Store review process and carries an additional 2 to 4 weeks of pre-submission testing, was using legacyReportFormat in its reporting screen query. The version in the App Store at the time of the field removal was 8 weeks old — built and submitted before the field deprecation was announced, and using the field in every reporting screen load.
The iOS reporting screen started returning null for all users immediately after the field removal deployed. The mobile team identified the broken field within 6 hours of the first user report, but the fix required submitting a new app version through App Store review — which took 6 days. During those 6 days, 14,200 iOS sessions showed broken reporting screens. The emergency app version bypassed the standard 4-week testing cycle, which meant it was submitted with only smoke testing completed. The founding session that set up the registry documented "operation checks enabled, 7-day window." It did not document the client compatibility window contract, which clients were required to register their operations, or the structural mismatch between the 7-day validation window and the mobile app's 6-to-8-week release cycle — a mismatch that meant the registry's protection did not extend to the client that needed it most.
Structural properties set by the GraphQL schema registry decision
Three structural properties are determined when a team decides how to implement GraphQL schema evolution governance across a distributed client base. None appear explicitly in the session that selects Apollo Federation, the session that configures the schema registry, or the session that defines the subgraph topology — they are the operational consequences of design choices made under the assumption that schema versioning, operation checks, and federation ownership together constitute a complete schema safety implementation.
Property 1: The breaking change detection surface and the client operation registration completeness. A schema registry detects breaking changes only against client operations that are registered with it. An unregistered client is invisible — the registry will approve any schema change that does not break the registered operations, regardless of what the unregistered client is using. The detection surface is the union of all registered client operation sets, and an unregistered client expands the undetected breaking change surface by the entire set of fields it queries. The registry setup must specify: which clients are required to register their operations before deploying to production, what the registration mechanism is for each client type (web clients can run rover continuously; mobile clients need a build-time registration step that extracts persisted queries from the app bundle), and what blocks registration-less deploys (ideally: the CI gate that runs rover client:check must fail if the client has not registered its operations with the schema registry, not just if the registered operations are broken). The API versioning decision record documents the field lifecycle policy — deprecation followed by a coordinated removal — that is the alternative to relying on automated operation checks alone, and the circumstances under which a formal deprecation process is required in addition to registry validation. The GraphQL vs. REST decision record documents the schema evolution trade-off at a higher level: REST versioning handles client compatibility through URL-level version namespacing, while GraphQL schema evolution requires the registry-based approach described here — and the registry approach is strictly weaker for clients that cannot or do not register.
Property 2: The client compatibility window and the release cycle constraint. The schema registry's validation period — the lookback window for operation data — defines the oldest client version the registry can detect breaking changes against. A 7-day window protects clients that release at least weekly. It does not protect clients with monthly or multi-month release cycles, which include all mobile apps that go through app store review processes, enterprise desktop apps with managed deployment windows, and embedded clients in hardware devices. The compatibility window must be set to the maximum expected age of the oldest actively supported client version, not the average. For platforms with mobile clients: the App Store review cycle is 2 to 7 days, and releases typically lag development by 4 to 8 weeks when release management and testing are included; the compatibility window must therefore be at minimum 60 days. For platforms with uncontrolled clients (public APIs with third-party consumers): the compatibility window is unbounded, and fields can only be removed through a formal deprecation process with an announced end-of-life date and explicit confirmation from all known consumers. The mobile deployment decision record documents the App Store release cycle constraint in full and how it differs from server-side deployment velocity — the critical asymmetry being that server-side teams can deploy a fix in minutes while a mobile fix requires days of review plus the existing client distribution tail, which means the compatibility window must be set conservatively rather than optimistically. The observability strategy decision record documents the client version telemetry that makes the compatibility window calculation tractable in practice: operation metrics tagged by client version allow the schema registry to determine not just whether a field is used, but which client versions are using it and what their distribution tail looks like, which is the data needed to confirm that all affected clients have migrated before a field is removed.
Property 3: The subgraph ownership contract and the entity key mutation surface. In a federated schema, each entity type is defined in one owning subgraph and can be extended by other subgraphs to add additional fields. The @key directive specifies the fields the gateway can use to look up the entity from any subgraph's reference resolver. An extension subgraph that asserts a @key field that the owning subgraph's reference resolver cannot handle breaks schema composition for the entire gateway — not just the affected subgraph. The ownership contract must specify: which subgraph owns each entity type's @key definition, whether extension subgraphs can assert additional @key fields (and if so, whether that requires modifying the owning subgraph's reference resolver), the approval process for federation topology changes (adding a new subgraph, adding an entity extension, changing a @key field), and the composition check that must pass in CI before any subgraph deploy (not just a lint check — a full gateway composition check using all current subgraph schemas). The GraphQL federation decision record documents the full federation topology decision, including the reference resolver contract and the composition rules that govern which @key field assertions are valid across subgraph boundaries. The microservices vs. monolith decision record documents the distributed team coordination cost that federation makes explicit: federation is a mechanism for distributing schema ownership across teams, but distributing ownership without a governance contract that specifies the approval process for cross-team schema interactions introduces composition failure modes that are invisible until they block a production deploy.
What the founding session records and what it omits
The founding GraphQL schema session typically records the schema composition model selected (schema stitching, federation v1, federation v2, or a BFF layer), the service domains and their schema ownership, the client applications that will consume the API, the type system conventions adopted (naming patterns, nullability defaults, pagination style), and the tooling selected for schema management (Apollo Server, GraphQL Yoga, Hasura, or another runtime). It may record the rationale for choosing federation over schema stitching — the team's preference for independent subgraph deployments, or the desire to avoid a gateway restart on each schema change. What it does not record is the client operation registration requirement: which clients must register, how registration is enforced at the CI level, and what happens to clients that cannot or do not register. It does not record the compatibility window specification: what the validation period should be for each client type, which client type has the longest release cycle, and what the minimum window is for the slowest client. It does not record the subgraph ownership contract: what "ownership" means for key field governance, whether extension subgraphs can assert new entity keys, and what the approval process is for federation topology changes that cross subgraph boundaries.
The client operation registration omission produces a failure that is structurally identical to the schema stitching null failure in the first case study, but occurs even with a fully configured schema registry. The registry is only as protective as the set of clients it knows about. An unregistered client that has been querying a field for months is invisible to the operation check that would otherwise block the field's removal. The failure manifests only after the field is removed — at which point the unregistered client has no protection and no warning. The founding session that sets up the registry typically focuses on the mechanics of rover integration and the CI gate configuration; the question of which clients must be registered before the gate provides meaningful protection is a governance question that the tooling does not enforce and the session does not surface. The CI/CD pipeline decision record documents the enforcement model for registration requirements: the mobile build pipeline must include a step that fails the build if the app's operation manifest has not been pushed to the schema registry, just as the server-side deploy pipeline fails if the schema check does not pass — both gates must be present for the protection to cover both client types.
The compatibility window omission produces a failure that is specific to mobile clients and is triggered by the structural mismatch between server-side deployment velocity and App Store review cycles. The founding session that configures the registry typically sets the validation period based on what is familiar from server-side deployment cadences: daily deploys suggest a 7-day window to account for the weekend, which is a reasonable default for server-side clients. The mobile app's release cycle — 6 to 10 weeks from code-complete to App Store distribution — is a different constraint order entirely. The founding session does not ask which client in the system has the longest release cycle, because the session's focus is the registry configuration rather than the client release calendar. The result is a validation period that protects the clients that are least likely to be broken by a field removal (web clients that deploy frequently and have short migration windows) and fails to protect the clients that are most likely to be broken (mobile clients with long-lived app versions that cannot be updated on demand). Linking the compatibility window specification to the release calendar of each client type — which is a product management artifact, not an engineering one — is the step that the founding session never takes.
The subgraph ownership omission produces a failure that is specific to federated schemas and is triggered by the natural evolution of a team that is new to federation's composition rules. Engineers who are comfortable with object-oriented inheritance or REST-style resource ownership tend to think of subgraph extensions as a form of augmentation that is local to the extending subgraph — "we're just adding fields to User in our subgraph." The @key directive appears in the extension because the Federation documentation shows it there, and the team's mental model does not include the constraint that @key is a claim about the owning subgraph's resolution capability, not just the extending subgraph's intention. The subgraph ownership contract makes this constraint explicit in terms that the team can apply: before asserting a new @key field in any subgraph extension, the extending team must confirm with the owning team that the owning subgraph's __resolveReference function can accept a reference object containing that field, or the owner must deploy a new version of the owning subgraph that adds the new key to its resolver before the extension is deployed. This coordination requirement is the concrete form of what "ownership" means in a federated schema — and it is the specification that the founding federation session consistently fails to produce.
The WhyChose decision extractor finds the founding GraphQL sessions in your ChatGPT and Claude export — the "should we move to GraphQL?" migration session, the "how do we handle breaking changes?" schema evolution discussion, the "should we move to Federation?" topology session, the "we need breaking change detection" registry setup session. It extracts the composition model selected and the options considered, the validation period configured and the rationale given, and the subgraph ownership decisions recorded — and surfaces the operational specifications that the session documented versus the ones it omitted. The GraphQL subscription decision record documents the analogous schema evolution concerns for subscription fields, where the compatibility window problem is compounded by the presence of long-lived WebSocket connections that may be using an old schema version for the duration of their connection. The API gateway decision record documents the gateway availability constraint that makes the federation ownership contract especially important: in a federated architecture the gateway's ability to start is contingent on a successful schema composition, so any subgraph deploy that causes a composition failure also blocks the gateway from picking up subsequent schema changes — making ownership governance failures disproportionately disruptive compared to other configuration errors.
The five ADR sections for a GraphQL schema registry decision
Section 1: Schema composition model selection. Specify whether the schema uses schema stitching, Apollo Federation v1, Apollo Federation v2, or a schema-per-service pattern with a backend-for-frontend layer, and document the rationale for the chosen model. Schema stitching composes schemas at gateway startup without subgraph ownership rules, which means any schema change requires a gateway restart and there are no composition-time enforcement mechanisms for cross-service schema consistency. Apollo Federation v2 enables independent subgraph deployments and push-based composition, but adds composition rules that enforce subgraph ownership contracts and create composition failure modes — notably the @key conflict failure — that do not exist in schema stitching. A BFF layer defers the composition problem by giving each client its own schema, at the cost of maintaining N-times-M schema definitions across N BFFs and M services. Document: the team ownership model (who owns the gateway process, who owns each subgraph or stitched service schema), the deployment model for schema changes (gateway restart required vs. push-based composition), and the consequence of the chosen model on schema change coordination cost. Federation increases independence for forward changes but increases coordination requirements for entity key changes; schema stitching requires less coordination for key changes but more for deployment sequencing. The API gateway decision record documents the gateway availability requirements that should constrain the composition model choice — a composition model that requires a gateway restart on each subgraph change is incompatible with a zero-downtime deployment requirement for the gateway process.
Section 2: Breaking change classification and client operation registration. Define what constitutes a breaking change in the context of this schema: field removal, field rename (a removal and addition), argument addition (if required, not optional), type change, nullability change to non-nullable, and enum value removal. Specify which clients must register their operations and the registration mechanism for each client type: web clients using rover client:push on each build, mobile clients using a build-time persisted query extraction step before App Store submission, BFF services using the same rover client:push workflow as web clients. Specify the CI check that enforces registration: the mobile build pipeline must fail if the app has not completed operation registration for the current build, not just warn; the schema check must fail if it detects that a proposed change would break a registered operation used by more than a configured threshold percentage of traffic in the validation window. Specify the detection threshold — the percentage of registered operations using a field that triggers a blocking classification versus a warning-only classification for rarely-used fields. The CI/CD pipeline decision record documents the pipeline gate configuration that makes these enforcement requirements operational: the schema check, the client registration check, and the composition check must all be configured as blocking gates rather than advisory warnings, because advisory warnings are routinely overridden under deploy pressure.
Section 3: Client compatibility window and field lifecycle policy. Specify the validation period for each client type and document the release cycle that determines the minimum compatibility window for each. For web clients that deploy on every merge to main: a 7-day window is sufficient. For mobile clients with a 4-to-8-week pre-submission cycle plus a 2-to-7-day App Store review: the minimum window is 60 days; 90 days is recommended for teams that do infrequent or seasonal mobile releases. For third-party API consumers with no controlled release cycle: the window is unbounded. Specify the field lifecycle stages: deprecation (the @deprecated directive is added to the field with a migration hint, and the field continues to serve all queries); end-of-life announcement (all registered client teams are notified of the planned removal date, with a removal date no sooner than the compatibility window from the announcement); confirmed migration (each registered client team confirms in writing that they have deployed a version using the replacement field and that the old field has zero usage in their registered operations); removal (the field is removed only after all registered clients have confirmed migration, or have been explicitly excluded from the protection with a documented rationale). For clients with incompatible release cycles — mobile clients in the 60-to-90-day window, third-party clients with unbounded cycles — document the exception process for emergency field removal, which requires explicit confirmation from those client teams that the field is unused in their current production version before removal proceeds. The API versioning decision record documents the formal versioning alternative to field lifecycle management for cases where the field lifecycle policy is insufficient to protect a client's compatibility window.
Section 4: Subgraph ownership contract and entity key mutation governance. For each entity type in the federated schema, specify: the owning subgraph and the team responsible for it, the @key fields the owning subgraph currently supports in its __resolveReference function (as a canonical list, not inferred from the schema), whether extension subgraphs can propose additional @key fields (and if so, the approval process — which must include a coordinated deploy in which the owning subgraph's resolver is updated to handle the new key before the extension subgraph is deployed), the composition check that must pass in CI for each subgraph deploy (a full multi-subgraph gateway composition check using all current subgraph schemas fetched from the schema registry, not a single-subgraph lint that cannot detect cross-subgraph @key conflicts), and the rollback procedure when a composition failure is detected after a subgraph deploy (which subgraph deploy caused the failure, the order of rollback — roll back the most recently deployed subgraph first and verify composition success after each rollback, not all at once). For new entity types, document the process for establishing ownership: the team proposing the entity type must coordinate with all teams that will extend it before the entity is deployed to production, so that the extension topology is known before the composition check is first run. The GraphQL federation decision record documents the full federation topology decision, including the reference resolver contract specification and the composition rules that govern which @key assertions are valid across subgraph boundaries and under what conditions adding a new entity key requires both an owning subgraph update and an extension subgraph update to be deployed in a specific order.
Section 5: Schema change rollout coordination and progressive rollout policy. Specify the coordination process for changes that affect multiple subgraphs simultaneously — additive changes (new fields, new types) versus breaking changes (removals, renames, type changes) — and the deployment order for multi-subgraph changes that must be sequenced to avoid transient composition failures during the deployment window. For additive multi-subgraph changes, specify which subgraph must deploy first (typically the owning subgraph that introduces the new field or entity, before any extending subgraph that references it) and what the gateway does during the transition window while only some subgraphs have deployed (it continues serving the previous composed schema until all subgraphs have published their changes). For high-risk schema changes — field removals that affect more than a configured threshold percentage of registered operations, entity key changes, or type changes — specify the progressive rollout policy: canary percentage for the schema change, traffic routing by client version header to direct old client versions to the schema version they were registered against, and the metric that determines whether to proceed with full rollout or roll back the schema change. Specify who is notified when the schema registry detects that a proposed change would affect more than a threshold percentage of registered operations: the notification must reach the team proposing the change, the teams owning the affected registered operations, and the team owning the gateway process, before the change is approved for deployment. The deployment strategy decision record documents the canary and progressive rollout mechanics at the infrastructure level that this schema-level policy must integrate with; the schema change rollout policy cannot be implemented independently of the deployment infrastructure's ability to route traffic by client version header or to roll back a deployed schema change without downtime.