Add upsertFlatUserCap GraphQL mutation

What does this MR do and why?

Adds the upsertFlatUserCap GraphQL mutation to set the default per-user budget cap for a subscription. Takes flatUserCap (Float, >= 0) and flatUserCapEnabled (Boolean), proxies to CDot's upsertBudgetCapSubscription.

This mutation must be called before upsertUserBudgetCapOverrides (MR4) — CDot requires a subscription budget cap record to exist before per-user overrides can be created.

Gated behind the budget_caps_graphql_api feature flag.

Related to https://gitlab.com/gitlab-org/gitlab/-/issues/595491

Stacked MR

# MR Description Status
1 !230214 (merged) Foundation — feature flag + client + shared concern Merged
2 !230218 (merged) Query — budget caps read API (BudgetCapsType, UserOverrideType, PORO) Merged
3 This MR Mutation — upsert flat user cap You are here
4 !230222 (merged) Mutation — upsert user budget cap overrides In review
5 !230226 (closed) Enable feature flag by default (draft, blocked by 3-4) Draft

How to set up and validate locally

Prerequisites: GDK running with CDot on localhost:5000. Use existing group saastestgroup.

  1. Enable the feature flag

    Feature.enable(:budget_caps_graphql_api)
  2. SaaS — Owner sets flat user cap

    Log in as owner of saastestgroup. Open GraphiQL at http://localhost:3000/-/graphql-explorer:

    mutation {
      upsertFlatUserCap(input: {
        namespacePath: "saastestgroup"
        flatUserCap: 25.0
        flatUserCapEnabled: true
      }) {
        flatUserCap
        flatUserCapEnabled
        errors
      }
    }

    Expected: flatUserCap: 25.0, flatUserCapEnabled: true, empty errors.

  3. Self-Managed — Instance admin sets flat user cap

    Log in as admin. Admin mode must be enabled — toggle admin mode from user settings (click avatar → Preferences → enable Admin Mode), or use a Personal Access Token (PAT) with api scope which bypasses admin mode automatically via sessionless_bypass_admin_mode!.

    Omit namespacePath for the SM path:

    mutation {
      upsertFlatUserCap(input: {
        flatUserCap: 100.0
        flatUserCapEnabled: true
      }) {
        flatUserCap
        flatUserCapEnabled
        errors
      }
    }

    Expected: flatUserCap: 100.0, flatUserCapEnabled: true, empty errors.

    Note: On Self-Managed, the admin policy condition in BasePolicy requires both user.admin? and an active admin mode session (Gitlab::Auth::CurrentUserMode#admin_mode?). Without admin mode enabled, the mutation returns a permission error. API requests authenticated with a PAT bypass this requirement automatically.

  4. Validation — Negative value rejected

    mutation {
      upsertFlatUserCap(input: {
        namespacePath: "saastestgroup"
        flatUserCap: -5.0
        flatUserCapEnabled: true
      }) {
        flatUserCap
        flatUserCapEnabled
        errors
      }
    }

    Expected: error "flatUserCap must be greater than or equal to 0"

  5. Unauthorized — non-owner (SaaS)

    Log in as a non-owner of saastestgroup, run mutation from step 2.

    Expected: "The resource that you are attempting to access does not exist or you don't have permission to perform this action"

  6. Feature flag disabled

    Feature.disable(:budget_caps_graphql_api)

    Run mutation from step 2 again.

    Expected: error "budget_caps_graphql_api feature flag is disabled"

  7. Verify MR4 overrides work after flat cap is set

    Re-enable the feature flag, then run upsertUserBudgetCapOverrides (MR4). CDot requires a subscription budget cap record to exist before per-user overrides can be created — step 2 creates that record.

    mutation {
      upsertUserBudgetCapOverrides(input: {
        namespacePath: "saastestgroup"
        overrides: [
          { userId: "gid://gitlab/User/1", cap: 15.0, enabled: true }
        ]
      }) {
        userOverrides {
          user { id username }
          cap
          capEnabled
        }
        errors
      }
    }

    Expected: updated overrides with resolved User objects.

MR acceptance checklist

These checklists encourage us to confirm any changes have been thoroughly considered.

  • This MR is behind a feature flag (budget_caps_graphql_api)
  • Request specs covering SaaS, self-managed, authorization, validation, and CDot error propagation
Edited by Suraj Tripathi

Merge request reports

Loading