[go: up one dir, main page]

Skip to content

Draft: Support new Self-Hosted Models Monetization with Provider-Based Access Control

What does this MR do and why?

Replaces scattered entitlement logic with two Single Sources of Truth:

Problem: Current system has fragmented provider support, scattered authorization logic, and complex business model requirements (seat-based features + provider entitlements).

Why Provider + Unit Primitive Model? Our SKU model requires both seat-based features AND provider entitlements:

  • GitLab Duo Pro ($14) – Seat access to features (duo_chat, code_generation)
  • GitLab Duo Self-hosting ($10) – Provider access (self_hosted_models)
  • Users need BOTH to access duo_chat through self-hosted models

Solution: Centralize all entitlement logic behind clean APIs that separate what (features), how (providers), and what's purchased (add-ons).

Architecture

SSoT for AddOnPurchases per resource and for Entitlement Checks

  1. AllowedAddOnPurchaseFinder.allowed_add_on_purchases(resource, apply_user_policy_filters: true) - Available purchases with smart filtering per resource (user, project, namespace, :instance)
  2. EntitlementChecker.check(resource, unit_primitive, provider:) - Static entitlement validation

Key Changes

  • UserAuthorizable Kitchen Sink Cleanup: Massive simplification by extracting add-on and entitlement specific logic:
    • DuoCore toggles (duo_core_features_enabled?) → AddOnPurchase.add_on_allowed?
    • Seat assignment filtering → AddOnPurchase.purchased_add_ons(user)
    • Add-on validation logic → Centralized per resource in single place
    • Entitlement check for unit primitive → new Entitlement Checker
  • CloudConnector Protection: No direct API calls outside EntitlementChecker
  • Token Generation: Simplified with provider-aware scopes generated with EntitlementChecker
  • Ai FeatureSettings filtering: Filters available feature settings based on instance entitlements

Migration

Phase 1 Core infrastructure with provider support (:gitlab_duo, :self_hosted_models, :platform, :amazon_q)

Phase 2 UserAuthorizable rollout via feature flags:

  • use_self_hosted_models_provider (self-hosted models)
  • Future: use_provider_for_duo_chat (individual features)

Phase 3 Direct API replacements:

# Before
AddOnPurchase.exists_for_unit_primitive?(:complete_code, :instance)

# After  
EntitlementChecker.check(:instance, :complete_code, provider: :gitlab_duo).allowed?

Usage

# Get enabled purchases (handles seat assignments, toggles)
purchases = AllowedAddOnPurchaseFinder.apply_user_policy_filters(user)

# Check feature entitlement
result = EntitlementChecker.check(user, :duo_chat, provider: :gitlab_duo)

# EntitlementResult structure
result.allowed?                    # Boolean: access granted
result.requires_add_on?           # Boolean: requires add-on purchase  
result.matching_add_ons          # Array: applicable add-on names
result.allowed_by_add_on_purchases # Array: valid AddOnPurchase objects
result.failure_scope             # Symbol: what failed (:provider, :unit_primitive)
result.failure_cause             # Symbol: why it failed (:not_found, :missing_required_add_on, :unsupported_license_plan etc.)

# Bulk discovery
entitlements = EntitlementChecker.new(user).allowed_entitlements_for([:gitlab_duo])

Based on the conversation and the current merge request description, here's how you can update the description to include local testing instructions:

Local Testing Instructions

To test the changes locally, follow these steps:

Prerequisites

  1. Clone the gitlab-cloud-connector repository
  2. Check out the branch from MR !177
  3. Follow the official testing guide
    1. Update your GDK's Gemfile:
         gem "gitlab-cloud-connector", require: 'gitlab/cloud_connector', 
         feature_category: :plan_provisioning, path: '/path/to/your/gitlab-cloud-connector/src/ruby'
    2. Add to Gitlab::Application:
        CloudConnector::Configuration.config_dir = '/path/to/your/gitlab-cloud-connector/config'
    3. Run bundle install and restart GDK

Setup Options

  • Option 1: Simplest - Use shipped data (Recommended)

      # Set environment variable to use data shipped with the gem
      export CLOUD_CONNECTOR_SELF_SIGN_TOKENS=1
  • Option 2: Seed Data In Rails console, execute:

      FactoryBot.create(:cloud_connector_access)
  • Option 3: Full Dogfooding

    • Use local gem in CustomersDot by modifying CDot Gemfile
    • Manually sync the license data

Verification

Test the new provider functionality in Rails console:

  # Check unit primitives with new providers property
  Gitlab::CloudConnector::DataModel::UnitPrimitive.find_by_name(:duo_chat)

  # Test provider functionality
  Gitlab::CloudConnector::DataModel::Provider.all

  # Test entitlement checking
  GitlabSubscriptions::EntitlementChecker.check(:instance, :duo_chat, provider: :gitlab_duo)

Note: When running Rails locally, it operates in SelfManaged mode and expects catalog data from CustomersDot. The environment variable CLOUD_CONNECTOR_SELF_SIGN_TOKENS=1 is the simplest way to use the data shipped with the gem.

Benefits

  • Centralized Logic: All entitlement complexity behind clean APIs
  • Provider Support: Easy addition of new providers (Amazon Q, Platform)
  • Business Model: Proper separation of seat vs provider entitlements
  • Maintainability: Single point of change for entitlement rules
  • Rollback Safety: Feature flags enable immediate rollback to legacy paths
Edited by Nikola Milojevic

Merge request reports

Loading