Skip to content

feat: add composition options#2595

Merged
Aenimus merged 12 commits intomainfrom
david/eng-9098-add-composition-options
Mar 10, 2026
Merged

feat: add composition options#2595
Aenimus merged 12 commits intomainfrom
david/eng-9098-add-composition-options

Conversation

@Aenimus
Copy link
Copy Markdown
Member

@Aenimus Aenimus commented Mar 6, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added --ignore-external-keys CLI flag to allow ignoring external entity keys during composition.
    • Added organization-level feature flag to enable/disable external key ignoring.
    • Refactored composition configuration to support multiple composition options.
  • Bug Fixes

    • Improved handling of external entity keys in federation composition.

Checklist

  • I have discussed my proposed changes in an issue and have received approval to proceed.
  • I have followed the coding standards of the project.
  • Tests or benchmarks have been added or updated.
  • Documentation has been updated on https://github.com/wundergraph/cosmo-docs.
  • I have read the Contributors Guide.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR refactors composition and federation parameter passing by introducing a unified CompositionOptions type, replacing individual boolean flags with structured options objects across composition/federation/normalization APIs, adding an ignoreExternalKeys feature flag in the controlplane, and converting type imports to type-only throughout.

Changes

Cohort / File(s) Summary
CompositionOptions Type Definition
composition/src/types/params.ts
Introduces CompositionOptions type with disableResolvabilityValidation and ignoreExternalKeys optional boolean fields.
Federation Parameter Types
composition/src/federation/params.ts, composition/src/v1/federation/params.ts
Adds FederateSubgraphsParams, FederateSubgraphsWithContractsParams, FederateSubgraphsContractParams, and FederationFactoryParams types to consolidate federation call signatures.
Normalization Parameter Types
composition/src/normalization/params.ts, composition/src/v1/normalization/params.ts
Introduces NormalizeSubgraphFromStringParams, NormalizeSubgraphParams, BatchNormalizeParams, and NormalizationFactoryParams parameter object types.
Federation Logic Refactoring
composition/src/federation/federation.ts, composition/src/v1/federation/federation-factory.ts
Updates federateSubgraphs* functions to accept options object parameter instead of disableResolvabilityValidation boolean; threads CompositionOptions through factory initialization and V1 implementations.
Normalization Logic Refactoring
composition/src/normalization/normalization.ts, composition/src/v1/normalization/normalization-factory.ts, composition/src/v1/normalization/utils.ts
Updates normalizeSubgraph* and batchNormalize functions to accept parameter objects with options field; adds ignoreExternalKeys gating for external key validation.
Composition Public API
composition/src/utils.ts, composition/src/index.ts
Updates composeSubgraphs signature to accept CompositionOptions object; exports new parameter types from /params modules.
CLI Integration
cli/src/commands/router/commands/compose.ts, cli/src/utils.ts
Adds --ignore-external-keys flag; updates compose command to pass unified options object to composition functions.
Feature Flag Infrastructure
controlplane/src/types/index.ts
Adds COMPOSITION_IGNORE_EXTERNAL_KEYS_FEATURE_ID constant and extends FeatureIds union with 'composition-ignore-external-keys'.
Controlplane Feature Integration
controlplane/src/core/bufservices/*/, controlplane/src/core/repositories/*
Replaces newCompositionOptions helper with direct compositionOptions object construction in 10+ files; fetches ignoreExternalKeys feature flag and passes through composition calls.
Controlplane Composition Exports
controlplane/src/core/composition/composition.ts, controlplane/src/core/util.ts
Imports CompositionOptions from @wundergraph/composition instead of local types; removes newCompositionOptions function.
Type-Only Imports (Composition)
composition/src/{federation,normalization,resolvability-graph,schema-building}/, composition/src/v1/*
Mass conversion of value imports to type-only imports for GraphQL types and internal data structures (50+ files).
Type-Only Imports (Tests)
composition/tests/v1/, composition/tests/
Converts test imports to type-only for data/definition types like Subgraph, ConfigurationData, FieldData, etc. (60+ test files).
Test Utilities
composition/tests/utils/utils.ts
Adds createSubgraph helper; updates normalization/federation test wrappers to accept CompositionOptions.
Test Rewrites
composition/tests/v1/normalization.test.ts, composition/tests/v1/directives/*.test.ts
Migrates tests from direct API usage to wrapper functions (normalizeSubgraphFailure, normalizeSubgraphSuccess, etc.); adds new test for ignoreExternalKeys feature.
Configuration & Dependency Updates
composition/tsconfig.json, composition/.eslintrc.json, composition/package.json, controlplane/package.json, cli/vite.config.ts
Reorders tsconfig compiler options; adds ESLint config for type imports; updates lint:fix scripts; changes controlplane composition dependency to workspace:*.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Rationale: Large-scope refactoring affecting 200+ files across composition, controlplane, and CLI with heterogeneous changes: (1) significant parameter-passing restructuring requiring verification across federation/normalization call chains; (2) high-volume repetitive type-only import changes across 110+ files with lower complexity but requiring spot-checking; (3) feature flag integration across 15+ service handlers; (4) test infrastructure overhaul affecting 60+ test files. Estimated breakdown: ~30 min for parameter threading logic verification, ~20 min for feature flag correctness in controlplane, ~15 min for test coverage validation, ~10 min for edge cases and integration points.

Possibly related PRs

  • #2616 — Adds the same COMPOSITION_IGNORE_EXTERNAL_KEYS_FEATURE_ID feature flag and threads ignoreExternalKeys through composition options in parallel controlplane services.
  • #2598 — Introduces the CompositionOptions object pattern and refactors composeSubgraphs call sites to accept structured options instead of boolean flags.
  • #2568 — Related behavior: implements resolvability-graph tracking of external edges and configurable external-key handling that integrates with the ignoreExternalKeys option introduced here.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 6, 2026

Codecov Report

❌ Patch coverage is 88.21549% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 44.27%. Comparing base (0d9169d) to head (b58b891).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
cli/src/commands/router/commands/compose.ts 11.11% 8 Missing ⚠️
...rvices/graph/setGraphRouterCompatibilityVersion.ts 11.11% 8 Missing ⚠️
...src/core/bufservices/subgraph/fixSubgraphSchema.ts 0.00% 8 Missing ⚠️
...e/bufservices/federated-graph/migrateFromApollo.ts 0.00% 5 Missing ⚠️
...ane/src/core/repositories/SchemaCheckRepository.ts 71.42% 4 Missing ⚠️
cli/src/utils.ts 50.00% 1 Missing ⚠️
...tion/src/v1/normalization/normalization-factory.ts 96.77% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #2595       +/-   ##
===========================================
- Coverage   62.37%   44.27%   -18.11%     
===========================================
  Files         244     1042      +798     
  Lines       25775   146582   +120807     
  Branches        0     9455     +9455     
===========================================
+ Hits        16078    64900    +48822     
- Misses       8322    79951    +71629     
- Partials     1375     1731      +356     
Files with missing lines Coverage Δ
composition/src/ast/utils.ts 93.13% <ø> (ø)
composition/src/buildASTSchema/buildASTSchema.ts 94.59% <100.00%> (ø)
composition/src/buildASTSchema/extendSchema.ts 58.33% <ø> (ø)
composition/src/errors/errors.ts 82.83% <100.00%> (ø)
composition/src/federation/federation.ts 100.00% <100.00%> (ø)
composition/src/index.ts 100.00% <100.00%> (ø)
composition/src/normalization/normalization.ts 100.00% <100.00%> (ø)
composition/src/resolvability-graph/graph-nodes.ts 98.03% <ø> (ø)
composition/src/resolvability-graph/graph.ts 93.21% <100.00%> (ø)
...graph/node-resolution-data/node-resolution-data.ts 91.30% <100.00%> (ø)
... and 58 more

... and 746 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 6, 2026

Router-nonroot image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-ff6dd3c708d9caa30190605452e9bacd7ea6fa0e-nonroot

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (8)
controlplane/src/core/bufservices/contract/updateContract.ts (1)

143-146: Deduplicate compositionOptions construction to prevent drift.

The same options object is built twice. A single local constant keeps future flag additions (like ignoreExternalKeys) consistent.

♻️ Suggested diff
+    const compositionOptions = {
+      disableResolvabilityValidation: req.disableResolvabilityValidation,
+      // ignoreExternalKeys: req.ignoreExternalKeys,
+    };
+
     await fedGraphRepo.update({
       targetId: graph.targetId,
@@
-      compositionOptions: {
-        // `@TODO` ignoreExternalKeys: ?,
-        disableResolvabilityValidation: req.disableResolvabilityValidation,
-      },
+      compositionOptions,
     });
@@
     const composition = await fedGraphRepo.composeAndDeployGraphs({
@@
-      compositionOptions: {
-        // `@TODO` ignoreExternalKeys: ?,
-        disableResolvabilityValidation: req.disableResolvabilityValidation,
-      },
+      compositionOptions,
       federatedGraphs: [

Also applies to: 161-164

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/bufservices/contract/updateContract.ts` around lines
143 - 146, The code builds the same compositionOptions object twice (used in
updateContract) which can drift — create a single local constant named
compositionOptions (containing disableResolvabilityValidation and any future
flags like ignoreExternalKeys) near the top of the function and reuse that
constant wherever compositionOptions is currently constructed (the two duplicate
sites), replacing the duplicate inline objects with references to the new
constant so flag additions stay consistent.
controlplane/src/core/bufservices/feature-flag/createFeatureFlag.ts (1)

208-211: Consider centralizing composition-options construction.

Line 208 repeats the same inline options shape used in multiple handlers (including the same ignoreExternalKeys TODO), which can drift over time.

♻️ Suggested direction
-        compositionOptions: {
-          // `@TODO` ignoreExternalKeys: ?,
-          disableResolvabilityValidation: req.disableResolvabilityValidation,
-        },
+        compositionOptions: buildCompositionOptions(req),
// e.g. shared helper colocated with request mappers
function buildCompositionOptions(
  req: { disableResolvabilityValidation: boolean },
) {
  return {
    // TODO: wire ignoreExternalKeys once exposed in request contract
    disableResolvabilityValidation: req.disableResolvabilityValidation,
  };
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/bufservices/feature-flag/createFeatureFlag.ts` around
lines 208 - 211, The inline compositionOptions object is duplicated across
handlers; extract construction into a single helper (e.g.,
buildCompositionOptions) and replace inline uses (including the object passed as
compositionOptions in createFeatureFlag) with a call like
buildCompositionOptions(req). The helper should accept the request shape (at
minimum req.disableResolvabilityValidation), return the options object and
include the existing ignoreExternalKeys TODO comment so it’s centralized for
future wiring. Update all other handlers that create the same inline shape to
use this helper to avoid drift.
composition/src/v1/normalization/params.ts (1)

4-7: Use import type and canonical GraphQL entrypoint.

All four imports on lines 4–7 are used exclusively in type annotations and should be declared as type-only imports. Additionally, DocumentNode should be imported from the canonical 'graphql' entrypoint rather than 'graphql/index'.

♻️ Suggested import adjustment
-import { Subgraph } from '../../subgraph/types';
-import { CompositionOptions } from '../../types/params';
-import { Graph } from '../../resolvability-graph/graph';
-import { DocumentNode } from 'graphql/index';
+import type { Subgraph } from '../../subgraph/types';
+import type { CompositionOptions } from '../../types/params';
+import type { Graph } from '../../resolvability-graph/graph';
+import type { DocumentNode } from 'graphql';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composition/src/v1/normalization/params.ts` around lines 4 - 7, Change the
four type-only imports to use type-only import syntax: import type { Subgraph }
from '...'; import type { CompositionOptions } from '...'; import type { Graph }
from '...'; and import type { DocumentNode } from 'graphql' (use the canonical
'graphql' entrypoint instead of 'graphql/index'); update the import statements
that reference Subgraph, CompositionOptions, Graph, and DocumentNode in
composition/src/v1/normalization/params.ts accordingly.
composition/src/federation/types.ts (1)

72-76: Avoid dual config inputs in FederateSubgraphsParams.

Keeping both disableResolvabilityValidation and options on the same params type allows conflicting values and unclear precedence. Prefer a single source of truth or an exclusive union.

♻️ Suggested type-shape cleanup
 export type FederateSubgraphsParams = {
   subgraphs: Array<Subgraph>;
-  disableResolvabilityValidation?: boolean;
-  options?: CompositionOptions;
+  options?: CompositionOptions;
   version?: SupportedRouterCompatibilityVersion;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composition/src/federation/types.ts` around lines 72 - 76,
FederateSubgraphsParams currently allows both disableResolvabilityValidation and
options (CompositionOptions) which can conflict; change the type to use a single
source of truth by replacing the current shape with an exclusive union or by
removing the standalone flag—e.g., keep FederateSubgraphsParams as either {
subgraphs: Array<Subgraph>; options?: CompositionOptions; version?:
SupportedRouterCompatibilityVersion } and remove disableResolvabilityValidation,
or define FederateSubgraphsParams as a union like { subgraphs: Array<Subgraph>;
options?: CompositionOptions & { disableResolvabilityValidation?: never };
version?: SupportedRouterCompatibilityVersion } | { subgraphs: Array<Subgraph>;
disableResolvabilityValidation?: boolean; options?: never; version?:
SupportedRouterCompatibilityVersion } so callers must supply one source of
config, and update all call sites that construct FederateSubgraphsParams
accordingly (look for usages of FederateSubgraphsParams,
disableResolvabilityValidation and CompositionOptions).
composition/tests/v1/router-configuration.test.ts (1)

678-678: Keep router compatibility version explicit in this test.

Relying on the default version can make this assertion drift when defaults change; keeping it explicit makes intent stable.

🧪 Suggested test hardening
-      const result = batchNormalize({ subgraphs: [monolith, reviews, users] }) as BatchNormalizationSuccess;
+      const result = batchNormalize({
+        subgraphs: [monolith, reviews, users],
+        version: ROUTER_COMPATIBILITY_VERSION_ONE,
+      }) as BatchNormalizationSuccess;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composition/tests/v1/router-configuration.test.ts` at line 678, The test
relies on batchNormalize(...) but doesn't pin the router compatibility version;
update the batchNormalize call that produces result (the invocation using
batchNormalize and its expected type BatchNormalizationSuccess) to pass an
explicit options object that sets the router compatibility/version (e.g.,
include a routerConfig or routerCompositionCompatibilityVersion field with the
intended version string) so the test no longer depends on the default.
composition/tests/v1/directives/directives.test.ts (1)

108-111: Drop the redundant NormalizationFailure cast.

normalizeSubgraphFailure(...) already returns NormalizationFailure, so the cast adds noise.

♻️ Suggested cleanup
-      const { errors, warnings } = normalizeSubgraphFailure(
-        nf,
-        ROUTER_COMPATIBILITY_VERSION_ONE,
-      ) as NormalizationFailure;
+      const { errors, warnings } = normalizeSubgraphFailure(
+        nf,
+        ROUTER_COMPATIBILITY_VERSION_ONE,
+      );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composition/tests/v1/directives/directives.test.ts` around lines 108 - 111,
The test currently casts the result of normalizeSubgraphFailure(...) to
NormalizationFailure unnecessarily; remove the redundant cast and use const {
errors, warnings } = normalizeSubgraphFailure(nf,
ROUTER_COMPATIBILITY_VERSION_ONE) to rely on the function's return type directly
(update any affected imports or type annotations if needed).
controlplane/src/core/bufservices/federated-graph/checkFederatedGraph.ts (1)

111-114: Resolve or track the inline TODO explicitly.

Leaving ignoreExternalKeys as an inline TODO in this hot path makes the migration easy to forget. Prefer either wiring it now (when request shape supports it) or removing the TODO and tracking it in a dedicated issue.

I can draft the follow-up issue template (scope, protobuf/API changes, and test updates) if you want.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/bufservices/federated-graph/checkFederatedGraph.ts`
around lines 111 - 114, The inline TODO for ignoreExternalKeys in the options
object inside checkFederatedGraph must be resolved: if the request type already
exposes this flag, wire it by adding ignoreExternalKeys: req.ignoreExternalKeys
to the options object alongside disableResolvabilityValidation; if the request
does not yet support it, remove the inline TODO and add a tracked follow-up
(create an issue and reference its ID in the code comment) describing required
protobuf/API changes and tests so it doesn't get forgotten. Ensure you update
the call site that assembles these options (the object containing
disableResolvabilityValidation) and any relevant request type handling to
reflect the chosen approach.
composition/src/v1/federation/federation-factory.ts (1)

2877-2877: Optional chaining is redundant here.

Since this.options is initialized with options ?? {} in the constructor (line 313), it can never be nullish. The ?. operator is unnecessary, though functionally harmless.

♻️ Suggested simplification
-    if (!this.options?.disableResolvabilityValidation && this.internalSubgraphBySubgraphName.size > 1) {
+    if (!this.options.disableResolvabilityValidation && this.internalSubgraphBySubgraphName.size > 1) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composition/src/v1/federation/federation-factory.ts` at line 2877, The
optional chaining on this.options is unnecessary because the constructor
initializes this.options = options ?? {}, so change the condition in the if that
checks disableResolvabilityValidation to use direct property access
(this.options.disableResolvabilityValidation) instead of
this.options?.disableResolvabilityValidation; keep the rest of the condition
(this.internalSubgraphBySubgraphName.size > 1) unchanged and ensure no
runtime-null checks are added elsewhere for this.options.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@composition/tests/v1/directives/directives.test.ts`:
- Line 162: The test title is misleading: the test declared in the test(...)
call reads "that a float can be coerced into a list of Int type" but the
assertions validate Float behavior; update the test title string in the test
declaration to correctly reference Float (e.g., change "Int" to "Float") so the
description matches the validation inside the test function (locate the
test(...) with that title in directives.test.ts and edit the title accordingly).

In `@controlplane/src/core/repositories/SubgraphRepository.ts`:
- Line 18: Change the runtime import of CompositionOptions to a type-only import
to avoid pulling it into emitted JS: replace the current import of
CompositionOptions with a type import (i.e., use "import type {
CompositionOptions } from '@wundergraph/composition'") and keep all usages in
SubgraphRepository (references at the CompositionOptions parameter/annotation
sites around the functions/lines where CompositionOptions is used) as type-only
annotations so the symbol is erased from runtime output.

---

Nitpick comments:
In `@composition/src/federation/types.ts`:
- Around line 72-76: FederateSubgraphsParams currently allows both
disableResolvabilityValidation and options (CompositionOptions) which can
conflict; change the type to use a single source of truth by replacing the
current shape with an exclusive union or by removing the standalone flag—e.g.,
keep FederateSubgraphsParams as either { subgraphs: Array<Subgraph>; options?:
CompositionOptions; version?: SupportedRouterCompatibilityVersion } and remove
disableResolvabilityValidation, or define FederateSubgraphsParams as a union
like { subgraphs: Array<Subgraph>; options?: CompositionOptions & {
disableResolvabilityValidation?: never }; version?:
SupportedRouterCompatibilityVersion } | { subgraphs: Array<Subgraph>;
disableResolvabilityValidation?: boolean; options?: never; version?:
SupportedRouterCompatibilityVersion } so callers must supply one source of
config, and update all call sites that construct FederateSubgraphsParams
accordingly (look for usages of FederateSubgraphsParams,
disableResolvabilityValidation and CompositionOptions).

In `@composition/src/v1/federation/federation-factory.ts`:
- Line 2877: The optional chaining on this.options is unnecessary because the
constructor initializes this.options = options ?? {}, so change the condition in
the if that checks disableResolvabilityValidation to use direct property access
(this.options.disableResolvabilityValidation) instead of
this.options?.disableResolvabilityValidation; keep the rest of the condition
(this.internalSubgraphBySubgraphName.size > 1) unchanged and ensure no
runtime-null checks are added elsewhere for this.options.

In `@composition/src/v1/normalization/params.ts`:
- Around line 4-7: Change the four type-only imports to use type-only import
syntax: import type { Subgraph } from '...'; import type { CompositionOptions }
from '...'; import type { Graph } from '...'; and import type { DocumentNode }
from 'graphql' (use the canonical 'graphql' entrypoint instead of
'graphql/index'); update the import statements that reference Subgraph,
CompositionOptions, Graph, and DocumentNode in
composition/src/v1/normalization/params.ts accordingly.

In `@composition/tests/v1/directives/directives.test.ts`:
- Around line 108-111: The test currently casts the result of
normalizeSubgraphFailure(...) to NormalizationFailure unnecessarily; remove the
redundant cast and use const { errors, warnings } = normalizeSubgraphFailure(nf,
ROUTER_COMPATIBILITY_VERSION_ONE) to rely on the function's return type directly
(update any affected imports or type annotations if needed).

In `@composition/tests/v1/router-configuration.test.ts`:
- Line 678: The test relies on batchNormalize(...) but doesn't pin the router
compatibility version; update the batchNormalize call that produces result (the
invocation using batchNormalize and its expected type BatchNormalizationSuccess)
to pass an explicit options object that sets the router compatibility/version
(e.g., include a routerConfig or routerCompositionCompatibilityVersion field
with the intended version string) so the test no longer depends on the default.

In `@controlplane/src/core/bufservices/contract/updateContract.ts`:
- Around line 143-146: The code builds the same compositionOptions object twice
(used in updateContract) which can drift — create a single local constant named
compositionOptions (containing disableResolvabilityValidation and any future
flags like ignoreExternalKeys) near the top of the function and reuse that
constant wherever compositionOptions is currently constructed (the two duplicate
sites), replacing the duplicate inline objects with references to the new
constant so flag additions stay consistent.

In `@controlplane/src/core/bufservices/feature-flag/createFeatureFlag.ts`:
- Around line 208-211: The inline compositionOptions object is duplicated across
handlers; extract construction into a single helper (e.g.,
buildCompositionOptions) and replace inline uses (including the object passed as
compositionOptions in createFeatureFlag) with a call like
buildCompositionOptions(req). The helper should accept the request shape (at
minimum req.disableResolvabilityValidation), return the options object and
include the existing ignoreExternalKeys TODO comment so it’s centralized for
future wiring. Update all other handlers that create the same inline shape to
use this helper to avoid drift.

In `@controlplane/src/core/bufservices/federated-graph/checkFederatedGraph.ts`:
- Around line 111-114: The inline TODO for ignoreExternalKeys in the options
object inside checkFederatedGraph must be resolved: if the request type already
exposes this flag, wire it by adding ignoreExternalKeys: req.ignoreExternalKeys
to the options object alongside disableResolvabilityValidation; if the request
does not yet support it, remove the inline TODO and add a tracked follow-up
(create an issue and reference its ID in the code comment) describing required
protobuf/API changes and tests so it doesn't get forgotten. Ensure you update
the call site that assembles these options (the object containing
disableResolvabilityValidation) and any relevant request type handling to
reflect the chosen approach.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 30bddf1a-21a1-405d-90f1-94218d31fcce

📥 Commits

Reviewing files that changed from the base of the PR and between 177a1c0 and 987a64a.

📒 Files selected for processing (44)
  • cli/src/commands/router/commands/compose.ts
  • cli/src/utils.ts
  • composition-go/index.global.js
  • composition/src/federation/federation.ts
  • composition/src/federation/types.ts
  • composition/src/index.ts
  • composition/src/normalization/normalization.ts
  • composition/src/normalization/params.ts
  • composition/src/types/params.ts
  • composition/src/v1/federation/federation-factory.ts
  • composition/src/v1/federation/params.ts
  • composition/src/v1/federation/types.ts
  • composition/src/v1/federation/utils.ts
  • composition/src/v1/normalization/normalization-factory.ts
  • composition/src/v1/normalization/params.ts
  • composition/src/v1/normalization/utils.ts
  • composition/tests/utils/utils.ts
  • composition/tests/v1/directives/directives.test.ts
  • composition/tests/v1/directives/fieldset-directives.test.ts
  • composition/tests/v1/directives/override.test.ts
  • composition/tests/v1/normalization.test.ts
  • composition/tests/v1/resolvability.test.ts
  • composition/tests/v1/router-configuration.test.ts
  • controlplane/src/core/bufservices/contract/createContract.ts
  • controlplane/src/core/bufservices/contract/updateContract.ts
  • controlplane/src/core/bufservices/feature-flag/createFeatureFlag.ts
  • controlplane/src/core/bufservices/feature-flag/deleteFeatureFlag.ts
  • controlplane/src/core/bufservices/feature-flag/enableFeatureFlag.ts
  • controlplane/src/core/bufservices/feature-flag/updateFeatureFlag.ts
  • controlplane/src/core/bufservices/federated-graph/checkFederatedGraph.ts
  • controlplane/src/core/bufservices/federated-graph/createFederatedGraph.ts
  • controlplane/src/core/bufservices/federated-graph/updateFederatedGraph.ts
  • controlplane/src/core/bufservices/graph/setGraphRouterCompatibilityVersion.ts
  • controlplane/src/core/bufservices/subgraph/deleteFederatedSubgraph.ts
  • controlplane/src/core/bufservices/subgraph/fixSubgraphSchema.ts
  • controlplane/src/core/bufservices/subgraph/moveSubgraph.ts
  • controlplane/src/core/bufservices/subgraph/publishFederatedSubgraph.ts
  • controlplane/src/core/bufservices/subgraph/updateSubgraph.ts
  • controlplane/src/core/composition/composer.ts
  • controlplane/src/core/composition/composition.ts
  • controlplane/src/core/repositories/FederatedGraphRepository.ts
  • controlplane/src/core/repositories/SubgraphRepository.ts
  • controlplane/src/core/util.ts
  • controlplane/src/types/index.ts
💤 Files with no reviewable changes (2)
  • controlplane/src/types/index.ts
  • composition/src/v1/federation/types.ts

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@composition/src/v1/normalization/normalization-factory.ts`:
- Line 433: NormalizationFactory declares and assigns the options field
(this.options / CompositionOptions) in the constructor but never reads it;
either remove the unused property and any constructor parameter/storage of
CompositionOptions from NormalizationFactory, or if it’s intended for future use
add a clear explanatory comment above the field and constructor parameter
stating its purpose (mirroring how FederationFactory uses this.options, e.g.,
this.options?.disableResolvabilityValidation) so the intent is explicit; update
the NormalizationFactory constructor and class fields accordingly to avoid the
unused-field lint warning.

In `@controlplane/emails/package.json`:
- Line 11: The package.json script "lint:fix" currently runs only "pnpm format"
which is misleading; either rename the script to "format" or make it actually
perform linting like other packages. Fix option A: rename the script key from
"lint:fix" to "format" and update any callers to use "format". Fix option B:
install/configure ESLint for the emails package and change the "lint:fix" script
to run both formatter and ESLint auto-fix (e.g., run the formatter and then
"eslint --fix" against the TypeScript files) so the "lint:fix" name matches
behavior; update any parent invocation if necessary. Ensure you update
package.json's scripts entry containing "lint:fix" accordingly and keep naming
consistent with other packages.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 56446123-ef31-416d-86d3-fefc70fc1e8e

📥 Commits

Reviewing files that changed from the base of the PR and between 987a64a and 8711715.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • cli/src/commands/router/commands/compose.ts
  • cli/vite.config.ts
  • composition-go/index.global.js
  • composition/src/v1/normalization/normalization-factory.ts
  • controlplane/emails/package.json
  • controlplane/package.json
  • controlplane/src/core/util.ts
  • controlplane/tsconfig.json
✅ Files skipped from review due to trivial changes (1)
  • controlplane/tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • cli/src/commands/router/commands/compose.ts

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
composition/src/v1/federation/federation-factory.ts (1)

2877-2877: Add direct regression tests for disableResolvabilityValidation on each public path.

Line 2877 is now option-gated; consider dedicated tests for federateSubgraphs, federateSubgraphsWithContracts, and federateSubgraphsContract to prevent future wiring regressions.

Also applies to: 3375-3427

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composition/src/v1/federation/federation-factory.ts` at line 2877, Add direct
regression tests to cover the option-gated path where
disableResolvabilityValidation is used: create unit tests that call
federateSubgraphs, federateSubgraphsWithContracts, and federateSubgraphsContract
with a setup of multiple subgraphs (so internalSubgraphBySubgraphName.size > 1)
and assert behavior when options.disableResolvabilityValidation is true vs
false. Ensure each public function (federateSubgraphs,
federateSubgraphsWithContracts, federateSubgraphsContract) is exercised
independently so the conditional at the start of the block referencing
this.options.disableResolvabilityValidation is hit, and verify that
resolvability validation is skipped when true and performed when false to catch
future wiring regressions.
composition/src/federation/params.ts (1)

1-5: Convert value imports to import type for type-only symbols.

Lines 1, 3, and Line 5 import type-only symbols as value imports. Although the project does not currently enforce strict type-only import settings, using import type is a best practice that clarifies intent and improves code maintainability.

♻️ Proposed change
-import { Subgraph } from '../subgraph/types';
+import type { Subgraph } from '../subgraph/types';
 import type { CompositionOptions } from '../types/params';
-import { SupportedRouterCompatibilityVersion } from '../router-compatibility-version/router-compatibility-version';
+import type { SupportedRouterCompatibilityVersion } from '../router-compatibility-version/router-compatibility-version';
 import type { ContractName } from '../types/types';
-import { ContractTagOptions } from './types';
+import type { ContractTagOptions } from './types';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composition/src/federation/params.ts` around lines 1 - 5, The file imports
Subgraph, SupportedRouterCompatibilityVersion, and ContractTagOptions as value
imports but they are type-only; change their imports to use "import type" (i.e.,
replace "import { Subgraph } from '../subgraph/types';", "import {
SupportedRouterCompatibilityVersion } from
'../router-compatibility-version/router-compatibility-version';", and "import {
ContractTagOptions } from './types';" with type-only imports) so the intent is
explicit and tree-shaking/type-only checks are clearer.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@composition/src/federation/params.ts`:
- Around line 1-5: The file imports Subgraph,
SupportedRouterCompatibilityVersion, and ContractTagOptions as value imports but
they are type-only; change their imports to use "import type" (i.e., replace
"import { Subgraph } from '../subgraph/types';", "import {
SupportedRouterCompatibilityVersion } from
'../router-compatibility-version/router-compatibility-version';", and "import {
ContractTagOptions } from './types';" with type-only imports) so the intent is
explicit and tree-shaking/type-only checks are clearer.

In `@composition/src/v1/federation/federation-factory.ts`:
- Line 2877: Add direct regression tests to cover the option-gated path where
disableResolvabilityValidation is used: create unit tests that call
federateSubgraphs, federateSubgraphsWithContracts, and federateSubgraphsContract
with a setup of multiple subgraphs (so internalSubgraphBySubgraphName.size > 1)
and assert behavior when options.disableResolvabilityValidation is true vs
false. Ensure each public function (federateSubgraphs,
federateSubgraphsWithContracts, federateSubgraphsContract) is exercised
independently so the conditional at the start of the block referencing
this.options.disableResolvabilityValidation is hit, and verify that
resolvability validation is skipped when true and performed when false to catch
future wiring regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bdb523ad-edb3-413a-92b6-da8e0c44e50c

📥 Commits

Reviewing files that changed from the base of the PR and between 8711715 and 28df1d0.

📒 Files selected for processing (13)
  • composition-go/index.global.js
  • composition/src/federation/federation.ts
  • composition/src/federation/params.ts
  • composition/src/federation/types.ts
  • composition/src/index.ts
  • composition/src/normalization/params.ts
  • composition/src/v1/federation/federation-factory.ts
  • composition/src/v1/federation/params.ts
  • composition/src/v1/normalization/normalization-factory.ts
  • composition/src/v1/normalization/params.ts
  • composition/tests/v1/directives/directives.test.ts
  • composition/tests/v1/directives/fieldset-directives.test.ts
  • composition/tsconfig.json
✅ Files skipped from review due to trivial changes (1)
  • composition/tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • composition/src/normalization/params.ts
  • composition/src/federation/federation.ts

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
composition/src/v1/normalization/params.ts (1)

45-49: Consider making noLocation optional for consistency.

Line 46 currently requires noLocation, which is stricter than the adjacent normalization params surface and can create avoidable call-site friction.

♻️ Suggested adjustment
 export type NormalizeSubgraphFromStringParams = {
-  noLocation: boolean;
+  noLocation?: boolean;
   sdlString: string;
   options?: CompositionOptions;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composition/src/v1/normalization/params.ts` around lines 45 - 49, The type
NormalizeSubgraphFromStringParams currently requires noLocation which is
inconsistent with nearby param types and causes unnecessary friction; update the
type declaration for NormalizeSubgraphFromStringParams so that noLocation is
optional (change noLocation to noLocation?: boolean) and then search for usages
of NormalizeSubgraphFromStringParams/any calls that construct this object to
ensure they handle an undefined noLocation (add defaulting where necessary,
e.g., treat undefined as false) so callers no longer must pass the flag.
composition/src/v1/normalization/utils.ts (1)

237-237: Add a focused regression test for ignoreExternalKeys.

Line 237 changes external-key classification semantics; please ensure there is an explicit test for both ignoreExternalKeys: true and false on this path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composition/src/v1/normalization/utils.ts` at line 237, Add focused
regression tests covering the conditional at the path where conditionalData is
checked against nf.options.ignoreExternalKeys: create two tests that exercise
the code path in composition/src/v1/normalization/utils.ts where "if
(!conditionalData && !nf.options.ignoreExternalKeys)" is evaluated — one with
nf.options.ignoreExternalKeys set to true and one set to false — asserting that
external-key classification behavior matches the intended semantics (external
keys ignored when true, classified when false); locate the normalization helper
that references conditionalData and nf (search for nf.options.ignoreExternalKeys
or the surrounding normalize/classify function) and call it with inputs that
produce conditionalData = falsy to explicitly cover the changed branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@composition/package.json`:
- Line 23: The "lint:fix" npm script in composition's package.json references
eslint but the package doesn't declare it; either add eslint to composition's
devDependencies (install a matching eslint version) so the "lint:fix" script can
call eslint directly, or update the "lint:fix" script to use pnpm exec (e.g.
change the command to use pnpm exec eslint ...) so eslint is resolved from the
workspace, and ensure the script name "lint:fix" in package.json is updated
accordingly.

In `@composition/src/v1/normalization/normalization-factory.ts`:
- Around line 3909-3914: The call to normalizeSubgraph(...) passes subgraph.name
which is undefined for unnamed entries; change it to pass the previously
computed per-subgraph fallback name (the variable assigned just above the call)
so normalizeSubgraph receives the actual fallback identifier instead of
undefined; update the argument for subgraphName in the normalizeSubgraph
invocation to use that computed fallback variable rather than subgraph.name.

---

Nitpick comments:
In `@composition/src/v1/normalization/params.ts`:
- Around line 45-49: The type NormalizeSubgraphFromStringParams currently
requires noLocation which is inconsistent with nearby param types and causes
unnecessary friction; update the type declaration for
NormalizeSubgraphFromStringParams so that noLocation is optional (change
noLocation to noLocation?: boolean) and then search for usages of
NormalizeSubgraphFromStringParams/any calls that construct this object to ensure
they handle an undefined noLocation (add defaulting where necessary, e.g., treat
undefined as false) so callers no longer must pass the flag.

In `@composition/src/v1/normalization/utils.ts`:
- Line 237: Add focused regression tests covering the conditional at the path
where conditionalData is checked against nf.options.ignoreExternalKeys: create
two tests that exercise the code path in
composition/src/v1/normalization/utils.ts where "if (!conditionalData &&
!nf.options.ignoreExternalKeys)" is evaluated — one with
nf.options.ignoreExternalKeys set to true and one set to false — asserting that
external-key classification behavior matches the intended semantics (external
keys ignored when true, classified when false); locate the normalization helper
that references conditionalData and nf (search for nf.options.ignoreExternalKeys
or the surrounding normalize/classify function) and call it with inputs that
produce conditionalData = falsy to explicitly cover the changed branch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d52f911f-2485-4816-8638-481e18ec2d0a

📥 Commits

Reviewing files that changed from the base of the PR and between 28df1d0 and fb2047d.

📒 Files selected for processing (86)
  • composition-go/index.global.js
  • composition/.eslintrc.json
  • composition/package.json
  • composition/src/ast/utils.ts
  • composition/src/buildASTSchema/buildASTSchema.ts
  • composition/src/buildASTSchema/extendSchema.ts
  • composition/src/errors/errors.ts
  • composition/src/errors/types.ts
  • composition/src/federation/federation.ts
  • composition/src/federation/params.ts
  • composition/src/federation/types.ts
  • composition/src/normalization/normalization.ts
  • composition/src/normalization/types.ts
  • composition/src/resolvability-graph/graph-nodes.ts
  • composition/src/resolvability-graph/graph.ts
  • composition/src/resolvability-graph/node-resolution-data/node-resolution-data.ts
  • composition/src/resolvability-graph/node-resolution-data/types/params.ts
  • composition/src/resolvability-graph/types/params.ts
  • composition/src/resolvability-graph/utils/types/params.ts
  • composition/src/resolvability-graph/utils/types/types.ts
  • composition/src/resolvability-graph/utils/utils.ts
  • composition/src/resolvability-graph/walker/entity-walker/entity-walker.ts
  • composition/src/resolvability-graph/walker/entity-walker/types/params.ts
  • composition/src/resolvability-graph/walker/root-field-walkers/root-field-walker.ts
  • composition/src/resolvability-graph/walker/root-field-walkers/types/params.ts
  • composition/src/router-configuration/types.ts
  • composition/src/router-configuration/utils.ts
  • composition/src/schema-building/ast.ts
  • composition/src/schema-building/types.ts
  • composition/src/schema-building/utils.ts
  • composition/src/subgraph/types.ts
  • composition/src/utils/string-constants.ts
  • composition/src/utils/types.ts
  • composition/src/utils/utils.ts
  • composition/src/v1/constants/constants.ts
  • composition/src/v1/constants/directive-definitions.ts
  • composition/src/v1/constants/non-directive-definitions.ts
  • composition/src/v1/constants/strings.ts
  • composition/src/v1/constants/type-nodes.ts
  • composition/src/v1/federation/federation-factory.ts
  • composition/src/v1/federation/utils.ts
  • composition/src/v1/federation/walkers.ts
  • composition/src/v1/normalization/directive-definition-data.ts
  • composition/src/v1/normalization/normalization-factory.ts
  • composition/src/v1/normalization/params.ts
  • composition/src/v1/normalization/types.ts
  • composition/src/v1/normalization/utils.ts
  • composition/src/v1/normalization/walkers.ts
  • composition/src/v1/schema-building/type-merging.ts
  • composition/src/v1/utils/utils.ts
  • composition/src/v1/warnings/params.ts
  • composition/src/v1/warnings/warnings.ts
  • composition/tests/utils/utils.ts
  • composition/tests/v1/contracts.test.ts
  • composition/tests/v1/directives/authorization-directives.test.ts
  • composition/tests/v1/directives/configure-description.test.ts
  • composition/tests/v1/directives/connect-configure-resolver.test.ts
  • composition/tests/v1/directives/directives.test.ts
  • composition/tests/v1/directives/external.test.ts
  • composition/tests/v1/directives/fieldset-directives.test.ts
  • composition/tests/v1/directives/inaccessible.test.ts
  • composition/tests/v1/directives/interface-object.test.ts
  • composition/tests/v1/directives/one-of.test.ts
  • composition/tests/v1/directives/override.test.ts
  • composition/tests/v1/directives/provides.test.ts
  • composition/tests/v1/directives/require-fetch-reasons.test.ts
  • composition/tests/v1/directives/semantic-non-null.test.ts
  • composition/tests/v1/directives/shareable.test.ts
  • composition/tests/v1/directives/subscription-filter.test.ts
  • composition/tests/v1/directives/v2-directives.test.ts
  • composition/tests/v1/entities.test.ts
  • composition/tests/v1/entity-interface.test.ts
  • composition/tests/v1/events.test.ts
  • composition/tests/v1/federation-factory.test.ts
  • composition/tests/v1/normalization.test.ts
  • composition/tests/v1/resolvability.test.ts
  • composition/tests/v1/router-configuration.test.ts
  • composition/tests/v1/type-merging.test.ts
  • composition/tests/v1/types/arguments.test.ts
  • composition/tests/v1/types/enums.test.ts
  • composition/tests/v1/types/inputs.test.ts
  • composition/tests/v1/types/interfaces.test.ts
  • composition/tests/v1/types/objects.test.ts
  • composition/tests/v1/types/scalars.test.ts
  • composition/tests/v1/types/unions.test.ts
  • composition/tests/v1/utils/utils.ts
✅ Files skipped from review due to trivial changes (8)
  • composition/src/v1/constants/directive-definitions.ts
  • composition/tests/v1/entity-interface.test.ts
  • composition/src/v1/schema-building/type-merging.ts
  • composition/tests/v1/types/objects.test.ts
  • composition/src/resolvability-graph/utils/utils.ts
  • composition/tests/v1/directives/configure-description.test.ts
  • composition/src/schema-building/types.ts
  • composition/tests/v1/directives/subscription-filter.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • composition/src/federation/federation.ts
  • composition/tests/v1/directives/override.test.ts
  • composition/tests/v1/router-configuration.test.ts

@github-actions github-actions bot added the router label Mar 9, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
controlplane/src/core/bufservices/graph/setGraphRouterCompatibilityVersion.ts (1)

121-185: ⚠️ Potential issue | 🔴 Critical

Propagate failure out of the transaction instead of always returning OK.

The error objects returned on Line 159 and Line 172 never leave the transaction callback, so this handler still falls through to the success response on Line 185. Since the version update happens on Line 124 before those checks, a failed composition/deployment can also leave the new compatibility version persisted unless the transaction is explicitly aborted.

💡 One way to fix this
-    await opts.db.transaction(async (tx) => {
+    const txResult = await opts.db.transaction(async (tx) => {
       const fedGraphRepo = new FederatedGraphRepository(logger, tx, authContext.organizationId);
-
-      await fedGraphRepo.updateRouterCompatibilityVersion(federatedGraph.id, version);
-
-      const auditLogRepo = new AuditLogRepository(opts.db);
-
-      await auditLogRepo.addAuditLog({
-        organizationId: authContext.organizationId,
-        organizationSlug: authContext.organizationSlug,
-        auditAction: `${federatedGraph.supportsFederation ? 'federated_graph' : 'monograph'}.updated`,
-        action: 'updated',
-        actorId: authContext.userId,
-        auditableType: `${federatedGraph.supportsFederation ? 'federated_graph' : 'monograph'}`,
-        auditableDisplayName: federatedGraph.name,
-        actorDisplayName: authContext.userDisplayName,
-        apiKeyName: authContext.apiKeyName,
-        actorType: authContext.auth === 'api_key' ? 'api_key' : 'user',
-        targetNamespaceId: federatedGraph.namespaceId,
-        targetNamespaceDisplayName: federatedGraph.namespace,
-      });
-
       const composition = await fedGraphRepo.composeAndDeployGraphs({
         actorId: authContext.userId,
         admissionConfig: {
           cdnBaseUrl: opts.cdnBaseUrl,
           webhookJWTSecret: opts.admissionWebhookJWTSecret,
@@
       if (composition.compositionErrors.length > 0) {
         return {
           response: {
             code: EnumStatusCode.ERR_SUBGRAPH_COMPOSITION_FAILED,
           },
@@
       if (composition.deploymentErrors.length > 0) {
         return {
           response: {
             code: EnumStatusCode.ERR_DEPLOYMENT_FAILED,
           },
@@
       }
+
+      await fedGraphRepo.updateRouterCompatibilityVersion(federatedGraph.id, version);
+
+      const auditLogRepo = new AuditLogRepository(opts.db);
+      await auditLogRepo.addAuditLog({
+        organizationId: authContext.organizationId,
+        organizationSlug: authContext.organizationSlug,
+        auditAction: `${federatedGraph.supportsFederation ? 'federated_graph' : 'monograph'}.updated`,
+        action: 'updated',
+        actorId: authContext.userId,
+        auditableType: `${federatedGraph.supportsFederation ? 'federated_graph' : 'monograph'}`,
+        auditableDisplayName: federatedGraph.name,
+        actorDisplayName: authContext.userDisplayName,
+        apiKeyName: authContext.apiKeyName,
+        actorType: authContext.auth === 'api_key' ? 'api_key' : 'user',
+        targetNamespaceId: federatedGraph.namespaceId,
+        targetNamespaceDisplayName: federatedGraph.namespace,
+      });
+
+      return null;
     });
+
+    if (txResult) {
+      return txResult;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@controlplane/src/core/bufservices/graph/setGraphRouterCompatibilityVersion.ts`
around lines 121 - 185, The transaction commits the version update before
composition/deployment checks and the early "return" inside the transaction
callback (after fedGraphRepo.updateRouterCompatibilityVersion and
composeAndDeployGraphs) doesn't escape the transaction, so failures still
commit; modify the flow so composition/deployment failures abort the transaction
and propagate the error to the outer handler — e.g., when composeAndDeployGraphs
returns compositionErrors or deploymentErrors, throw a specific Error (or
reject) carrying the intended error response instead of returning from inside
the tx callback, then catch that Error around opts.db.transaction(...) and
return its payload; reference fedGraphRepo.updateRouterCompatibilityVersion,
composeAndDeployGraphs, and the transaction callback to locate where to throw
and where to catch.
🧹 Nitpick comments (1)
controlplane/test/integrations.test.ts (1)

324-360: This only covers greenfield enablement.

Lines 324-360 republish everything after enabledFeatures is set, so this test will not catch rollout bugs where already-published inputs stay blocked because their cached composability was computed before the org feature changed. Please add one regression that flips the feature for existing subgraphs/graphs too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/integrations.test.ts` around lines 324 - 360, Test
currently only validates greenfield enablement; add a regression that verifies
flipping the feature for already-published inputs works. Specifically: use
SetupTest without enabledFeatures to create namespace via createNamespace,
publish the externalKey and keySource subgraphs with publishFederatedSubgraph
and create the federated graph with createFederatedGraph, then simulate enabling
the COMPOSITION_IGNORE_EXTERNAL_KEYS_FEATURE_ID (e.g., re-run or reconfigure
SetupTest with enabledFeatures including that feature or restart the
server/client with the flag) and re-attempt composition/republishing of the
graph/subgraphs; assert response codes are OK and compositionErrors length is 0
to ensure previously-published inputs are no longer blocked (reference
SetupTest, enabledFeatures, publishFederatedSubgraph, createFederatedGraph,
createNamespace).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@controlplane/src/core/bufservices/contract/createContract.ts`:
- Around line 108-111: The early rejection uses cached sourceGraph.isComposable
without considering the org feature ignoreExternalKeys (fetched via
orgRepo.getFeature and COMPOSITION_IGNORE_EXTERNAL_KEYS_FEATURE_ID); update
createContract to re-evaluate composability under the effective composition
options by invoking the composition routine (the same composition used later)
with ignoreExternalKeys set according to ignoreExternalKeysFeature before
returning the early error (i.e., compute a fresh composition result and check
its isComposable instead of relying solely on the stored
sourceGraph.isComposable); apply the same change to the later composition path
so both checks use the effective composition options.

---

Outside diff comments:
In
`@controlplane/src/core/bufservices/graph/setGraphRouterCompatibilityVersion.ts`:
- Around line 121-185: The transaction commits the version update before
composition/deployment checks and the early "return" inside the transaction
callback (after fedGraphRepo.updateRouterCompatibilityVersion and
composeAndDeployGraphs) doesn't escape the transaction, so failures still
commit; modify the flow so composition/deployment failures abort the transaction
and propagate the error to the outer handler — e.g., when composeAndDeployGraphs
returns compositionErrors or deploymentErrors, throw a specific Error (or
reject) carrying the intended error response instead of returning from inside
the tx callback, then catch that Error around opts.db.transaction(...) and
return its payload; reference fedGraphRepo.updateRouterCompatibilityVersion,
composeAndDeployGraphs, and the transaction callback to locate where to throw
and where to catch.

---

Nitpick comments:
In `@controlplane/test/integrations.test.ts`:
- Around line 324-360: Test currently only validates greenfield enablement; add
a regression that verifies flipping the feature for already-published inputs
works. Specifically: use SetupTest without enabledFeatures to create namespace
via createNamespace, publish the externalKey and keySource subgraphs with
publishFederatedSubgraph and create the federated graph with
createFederatedGraph, then simulate enabling the
COMPOSITION_IGNORE_EXTERNAL_KEYS_FEATURE_ID (e.g., re-run or reconfigure
SetupTest with enabledFeatures including that feature or restart the
server/client with the flag) and re-attempt composition/republishing of the
graph/subgraphs; assert response codes are OK and compositionErrors length is 0
to ensure previously-published inputs are no longer blocked (reference
SetupTest, enabledFeatures, publishFederatedSubgraph, createFederatedGraph,
createNamespace).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a396958c-81fa-4ee9-9b6e-c294be6f17e3

📥 Commits

Reviewing files that changed from the base of the PR and between fb2047d and 52661b1.

📒 Files selected for processing (24)
  • cli/src/commands/router/commands/compose.ts
  • controlplane/src/core/bufservices/contract/createContract.ts
  • controlplane/src/core/bufservices/contract/updateContract.ts
  • controlplane/src/core/bufservices/feature-flag/createFeatureFlag.ts
  • controlplane/src/core/bufservices/feature-flag/deleteFeatureFlag.ts
  • controlplane/src/core/bufservices/feature-flag/enableFeatureFlag.ts
  • controlplane/src/core/bufservices/feature-flag/updateFeatureFlag.ts
  • controlplane/src/core/bufservices/federated-graph/checkFederatedGraph.ts
  • controlplane/src/core/bufservices/federated-graph/createFederatedGraph.ts
  • controlplane/src/core/bufservices/federated-graph/migrateFromApollo.ts
  • controlplane/src/core/bufservices/federated-graph/updateFederatedGraph.ts
  • controlplane/src/core/bufservices/graph/setGraphRouterCompatibilityVersion.ts
  • controlplane/src/core/bufservices/monograph/publishMonograph.ts
  • controlplane/src/core/bufservices/subgraph/checkSubgraphSchema.ts
  • controlplane/src/core/bufservices/subgraph/deleteFederatedSubgraph.ts
  • controlplane/src/core/bufservices/subgraph/fixSubgraphSchema.ts
  • controlplane/src/core/bufservices/subgraph/moveSubgraph.ts
  • controlplane/src/core/bufservices/subgraph/publishFederatedSubgraph.ts
  • controlplane/src/core/bufservices/subgraph/updateSubgraph.ts
  • controlplane/src/core/repositories/OrganizationRepository.ts
  • controlplane/src/core/repositories/SchemaCheckRepository.ts
  • controlplane/src/core/repositories/SubgraphRepository.ts
  • controlplane/src/types/index.ts
  • controlplane/test/integrations.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • controlplane/src/core/bufservices/feature-flag/createFeatureFlag.ts
  • controlplane/src/core/bufservices/subgraph/updateSubgraph.ts
  • controlplane/src/core/bufservices/subgraph/moveSubgraph.ts
  • cli/src/commands/router/commands/compose.ts
  • controlplane/src/core/repositories/SubgraphRepository.ts
  • controlplane/src/core/bufservices/subgraph/deleteFederatedSubgraph.ts

Copy link
Copy Markdown
Contributor

@wilsonrivera wilsonrivera left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Aenimus Aenimus marked this pull request as ready for review March 10, 2026 09:55
@Aenimus Aenimus enabled auto-merge (squash) March 10, 2026 09:57
@Aenimus Aenimus merged commit 1cba9ab into main Mar 10, 2026
50 checks passed
@Aenimus Aenimus deleted the david/eng-9098-add-composition-options branch March 10, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants