graphql-hive / graphql-hive/federation-composition
Cannot compose `_service.sdl` from `@apollo/subgraph` on federation v2.8+: duplicate `@federation__context` / `@federation__fromContext`
- Dominant language
- TypeScript
- Stars
- 53
- Forks
- 8
- Avg merge
- 20h 50m
- Merged PRs (30d)
- 10
Description
## Summary
A subgraph built with `@apollo/subgraph` that links federation v2.8 or later cannot be composed from its own `_service.sdl`. Composition fails with:
```
There can be only one directive named "@federation__context".
There can be only one directive named "@federation__fromContext".
```
`@apollo/composition` accepts the same input.
## Reproduction
```js
// npm i @apollo/subgraph @theguild/federation-composition graphql
import { buildSubgraphSchema } from '@apollo/subgraph';
import { composeServices, compositionHasErrors } from '@theguild/federation-composition';
import { graphqlSync, parse } from 'graphql';
const typeDefs = parse(`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.9", import: ["@key"])
type Product @key(fields: "slug") { slug: String! name: String! }
type Query { product(slug: String!): Product }
`);
// The SDL a running subgraph serves.
const sdl = graphqlSync({
schema: buildSubgraphSchema([{ typeDefs, resolvers: {} }]),
source: '{ _service { sdl } }',
}).data._service.sdl;
const result = composeServices([{ name: 's', url: 'http://localhost:4000/graphql', typeDefs: parse(sdl) }]);
console.log(compositionHasErrors(result) ? result.errors.map((e) => e.message) : 'OK');
```
Output:
```
[
'[s] There can be only one directive named "@federation__context".',
'[s] There can be only one directive named "@federation__fromContext".'
]
```
## Affected versions
Reproduced on 0.24.1 and 0.25.0.
The boundary is the linked federation version. Same repro, varying only the `@link` url:
| federation | directive definitions in `_service.sdl` | result |
| ---------- | --------------------------------------- | ------ |
| v2.0 | 10 | OK |
| v2.3 | 12 | OK |
| v2.5 | 14 | OK |
| v2.7 | 15 | OK |
| v2.8 | 17 | FAIL |
| v2.9 | 19 | FAIL |
v2.8 is where `@context` and `@fromContext` entered the spec.
## Comparison with @apollo/composition
Same SDL, same call shape:
```
@apollo/composition 2.14.4 : OK
@theguild/federation-composition 0.25.0 : FAIL (2)
```
## Why it matters
`_service.sdl` is what a running subgraph serves, so it is the SDL you get by introspecting a live service instead of reading an authored file. Anything composing from a live subgraph hits this: `hive dev --url ...`, registry ingestion via introspection, and CI gates that compose a service's emitted SDL.
Note that only these two collide. The other 17 definitions `_service.sdl` prints are tolerated, which is what makes this look like an oversight rather than a deliberate rejection of subgraph-supplied definitions.
## Workaround
Strip directive definitions from the composer's input before calling `composeServices`. Dropping only the two colliding definitions is enough. In our case, dropping the whole `link` / `key` / `federation__` family produced a byte-identical supergraph, so the coarser filter is safe too.
## Possibly related
#85 tracks `@context` and `@fromContext` composition support and may share a root cause.
Contributor guide
No contributing guide indexed for this repository
Research direction
Run the supplied reproduction using buildSubgraphSchema, graphqlSync, and composeServices, then compare the emitted SDL and composition behavior across federation v2.7 and v2.8. Trace how composeServices handles the @federation__context and @federation__fromContext definitions. Done means composing the emitted _service.sdl succeeds for federation v2.8 and later without duplicate-directive errors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, typescript
- Domain
- api, backend-api-design
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100