Fix Code Suggestions direct access on cloud-connected instances

What does this MR do and why?

On GitLab.com, POST /api/v4/code_suggestions/direct_access always returns a 400 error ("No Code Completion model provided"), even when the customer assigned a model through namespace-level Model Selection. Because of this, the language server cannot use the direct connection to the AI Gateway and falls back to the slower, indirect path through the monolith.

A first fix (!248253 (merged)) was reverted (!248825 (merged)) because it caused an incident (INC-12778): the token requests did not carry a root namespace, so CustomersDot rejected the usage billing checks with HTTP 422 "Root namespace can't be blank".

This MR fixes both problems together, in three commits:

  1. Send namespace context on the token request. The token request to the AI Gateway now includes the x-gitlab-root-namespace-id, x-gitlab-namespace-id, and x-gitlab-project-id headers. The client resolves them from the project and the user's governing namespace — the same way the completions endpoint and ai_gateway_public_headers already do. The AI Gateway trusts these headers only on this server-to-server call (SaaS realm) and embeds the root namespace as a claim in the JWT it issues. This gives CustomersDot a verified value for the usage billing check.
  2. Run the endpoint checks before the token request. The feature, paid-namespace, and Duo-context checks now run first. The endpoint never requests a token for a user without a governing namespace — it returns 422 (missing_default_duo_group) instead.
  3. Allow direct access tokens on cloud-connected instances, behind a feature flag. The 400 guard from !237255 (merged) checks the Ai::FeatureSetting record, which exists only on instances that use Duo Self-Hosted. The guard now applies only when a self-hosted AI Gateway is configured. Without one, the token request falls back to the cloud connector, as it did before 19.1. The new behavior is gated by the code_suggestions_direct_access_cloud_connected feature flag (default off, actor: user). With the flag off, GitLab.com keeps the current production behavior, so rollback is instant.

The error message for the real Duo Self-Hosted case ("unpinned model") is kept.

Rollout and rollback

  • The feature flag rolls out gradually (team members → percentage of users). See the rollout issue.
  • Rollback = disable the feature flag. No revert is needed.
  • During rollout, watch the CustomersDot quota-denials dashboard and the AI Gateway USAGE_QUOTA_CHECK_TOTAL{result="deny", realm="saas"} metric.

Out of scope

Hardening on the AI Gateway side (prefer the JWT claim over the client-editable header on direct calls, fail safely when the namespace is absent) is tracked separately.

References

Screenshots or screen recordings

No UI changes.

How to set up and validate locally

Cloud-connected path (the fix)

  1. Make sure your GDK simulates SaaS (export GITLAB_SIMULATE_SAAS=1) and has no self-hosted AI Gateway URL:

    # rails console
    Gitlab::CurrentSettings.update!(ai_gateway_url: nil)
    ::Ai::FeatureSetting.find_by(feature: :code_completions)&.destroy
  2. Enable the feature flag for your user:

    Feature.enable(:code_suggestions_direct_access_cloud_connected, User.find_by(username: 'root'))
  3. Call the endpoint with a user who has a Duo seat and a default Duo namespace:

    curl -X POST -H "Authorization: Bearer $TOKEN" \
      "http://gdk.test:3000/api/v4/code_suggestions/direct_access" \
      -H "Content-Type: application/json" \
      -d '{"project_path": "group/project"}'
  4. Expected: 201 with a token. The outgoing token request to the AI Gateway carries x-gitlab-root-namespace-id. With a local AI Gateway and its CustomersDot mock server (lib/usage_quota/mock_server.py in ai-assist), the usage quota check passes.

  5. Disable the feature flag and repeat: the endpoint returns 400 again (current production behavior).

Self-hosted path (behavior is kept)

  1. Configure a self-hosted AI Gateway URL:

    Gitlab::CurrentSettings.update!(ai_gateway_url: 'http://localhost:5052')
  2. Keep the code completions feature setting unpinned (no record).

  3. Call the same endpoint — it still returns the 400 error with the clear message, same as before this MR.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Merge request reports

Loading