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
- AllowedAddOnPurchaseFinder.allowed_add_on_purchases(resource, apply_user_policy_filters: true) - Available purchases with smart filtering per resource (user, project, namespace, :instance)
- 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
- Clone the gitlab-cloud-connector repository
- Check out the branch from MR !177
- Follow the official testing guide
- 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' - Add to
Gitlab::Application:CloudConnector::Configuration.config_dir = '/path/to/your/gitlab-cloud-connector/config' - Run
bundle installand restart GDK
- Update your GDK's Gemfile:
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