Refuse Code Suggestions and Amazon Q requests on an offline license

What does this MR do and why?

This is the third of three merge requests, split from one change to limit blast radius. It moves Code Suggestions and Amazon Q onto the resolver.

Depends on !260773 (merged), the resolver MR, which adds Gitlab::AiGateway.request_url!, access_token_url! and NoCloudConnectorTokenError.

Depends on !265183, the chat and completions callers MR, which makes the resolver follow the token decision.

The merge order is !260773 (merged), then !265183, then this MR, which closes the work item.

On an offline cloud license, a GitLab Duo request can reach cloud.gitlab.com when Local AI Gateway URL is nil or a feature uses a GitLab-managed model. It carries no credential that host issued.

On an offline cloud license, the resolver now refuses a request, naming the feature setting, unless CloudConnector::Tokens#use_self_signed_token? would self-sign the token.

Code Suggestions and Amazon Q now resolve their URL through the resolver. Each of the three callers, the Code Suggestions base URL, the direct access token and the Amazon Q client, passes the unit primitive its token uses, so the resolver and the token decide on the same feature setting. When the resolver refuses, the caller stops before the request, in the failure shape its own callers already handle. The message names the setting to change.

  • Code Suggestions completions and the token request return a 400.
  • Amazon Q reports it where each caller reports a failure: the admin settings form, the health check, the /q failure note, or a log entry.

What changes for users:

  • A refused Code Suggestions completion names its setting, Code Generation or Code Completion.
  • With cache_code_suggestions_direct_access_token on, a cached direct access token is now refused on an offline cloud license the same way as a fresh one, with the token step's 400 instead of a 500.
  • Otherwise, direct access and Amazon Q behave as before. Their specs now pin the unit primitive the resolver asks about.
Where each request resolves its URL, and how it reports the refusal
Request Resolved at Unit primitive passed Result on an offline cloud license
Code Suggestions completions, POST /api/v4/code_suggestions/completions ee/lib/code_suggestions/model_details/base.rb, rescued in ee/lib/api/code_suggestions.rb :complete_code or :generate_code, the task's, or :amazon_q_integration when Amazon Q is connected a 400 in the shape of the existing missing-feature-setting error
Code Suggestions token, POST /api/v4/code_suggestions/direct_access ee/lib/gitlab/llm/ai_gateway/code_suggestions_client.rb :complete_code the same 400, also when its raw Code Completion row is GitLab-managed
Amazon Q ee/lib/gitlab/llm/q_ai/client.rb :amazon_q_integration NoCloudConnectorTokenError, which each caller reports as its own failure: an admin settings error on :base (create_service.rb, destroy_service.rb under ee/app/services/ai/amazon_q/), a failed health check result (ee/app/services/cloud_connector/status_checks/probes/amazon_q/end_to_end_probe.rb), the /q failure note (amazon_q_trigger_service.rb), or a log entry for a webhook event (ee/app/models/concerns/integrations/base/amazon_q.rb)

Refusal path

flowchart LR
    R["a Duo request<br/>builds its URL"] --> RU["Gitlab::AiGateway.request_url!<br/>or access_token_url!<br/>with the caller's unit primitive"]
    RU --> L{"offline<br/>cloud license?"}
    L -->|no| S["URL returned<br/>request sent"]
    L -->|yes| U{"Local AI Gateway<br/>URL set?"}
    U -->|no| E["raise NoCloudConnectorTokenError<br/>naming the setting<br/>no request sent"]
    U -->|yes| T{"would the token<br/>be self-signed?"}
    T -->|yes| S
    T -->|no| E
    E --> X["each caller handles it<br/>in its own failure shape"]
    X --> CS["Code Suggestions API and CodeSuggestionsClient<br/>400"]
    X --> Q["QAi::Client callers<br/>settings error, probe result, /q failure note, log"]

An AI coding assistant wrote this change and ran the checks below in a simulated environment: a GitLab source checkout and a 19.4.1 test instance.

References

Screenshots or screen recordings

No UI change. The health check screenshots are in the resolver MR.

How to set up and validate locally

Steps, and what they showed on 19.4.1
  1. With an offline cloud license, assign a self-hosted model to Code Completion and clear Local AI Gateway URL.
  2. POST /api/v4/code_suggestions/completions with a current_file, and POST /api/v4/code_suggestions/direct_access. Expected: the requests return a 400 naming Local AI Gateway URL only, and nothing connects to cloud.gitlab.com.
  3. Set the URL and repeat. Expected: the local AI gateway serves the completion.
  4. Set Code Completion to vendored with Ai::FeatureSettings::UpdateService in a Rails console and repeat the token request. Expected: a 400 naming the model assignment only.
  5. bundle exec rspec on the changed spec files.
On 19.4.1 with an offline cloud license Before With this change
Token request, URL set, Code Completion GitLab-managed reached cloud.gitlab.com, 401 the 400 naming Code completion
Completions request, URL cleared, Code Completion self-hosted reached cloud.gitlab.com, 401 the 400 naming the URL, as did the token request
Completions, URL set, self-hosted model the local AI gateway served completions the same

These instance results come from the combined change in the original merge request, before it was split. Amazon Q rests on its specs alone, since it needs an Amazon Q connection to AWS.

  • On a test VM with this MR applied on top of the chat and completions callers MR, the 8 changed spec files and amazon_q_spec pass with 406 examples, no failures and 12 pending. No changed line lacks coverage.
  • The 14 spec files that MR changes pass with 569 examples and no failures with this MR on top.
  • With the unit primitive reverted in the three callers, the 4 specs that pin it fail on 8 of 295 examples.
  • With the offline check run after the token cache read, as before this change, the two cached-token examples fail: the client returns the cached token, and the endpoint answers 500.
  • RuboCop is clean on the 18 Ruby files changed against the callers 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.

Edited by Andrew Dunn

Merge request reports

Loading
Loading