diff --git a/config/audit_events/types/model_selection_feature_changed.yml b/config/audit_events/types/model_selection_feature_changed.yml new file mode 100644 index 0000000000000000000000000000000000000000..7860318e388c116b1d94a865907c8252804efb58 --- /dev/null +++ b/config/audit_events/types/model_selection_feature_changed.yml @@ -0,0 +1,10 @@ +--- +name: model_selection_feature_changed +description: A model selection feature had its configuration changed +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/547982 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/194862 +feature_category: self-hosted_models +milestone: '18.2' +saved_to_database: true +streamed: true +scope: [Instance, Group, Project, User] diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index 90ecb8469049b5989609aa38418b8d417d68b1c8..56f60c4bee91f6e11ab28cfcf76679997606ed40 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -544,6 +544,7 @@ Audit event types belong to the following product categories. | Type name | Event triggered when | Saved to database | Introduced in | Scope | |:----------|:---------------------|:------------------|:--------------|:------| +| [`model_selection_feature_changed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/194862) | A model selection feature had its configuration changed | {{< icon name="check-circle" >}} Yes | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/issues/547982) | Instance, Group, Project, User | | [`self_hosted_model_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165303) | A new self-hosted model configuration was added | {{< icon name="check-circle" >}} Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/477999) | Instance | | [`self_hosted_model_destroyed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165321) | A new self-hosted model configuration was destroyed | {{< icon name="check-circle" >}} Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/477999) | Instance | | [`self_hosted_model_feature_changed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165489) | A self-hosted model feature had its configuration changed | {{< icon name="check-circle" >}} Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/463215) | Instance | diff --git a/ee/app/services/ai/model_selection/update_service.rb b/ee/app/services/ai/model_selection/update_service.rb index a6fd96261b74996ce8f5accae4a34004e7fdd5a4..03fdbfdbf05cc859a20c06f50054efcbdda13942 100644 --- a/ee/app/services/ai/model_selection/update_service.rb +++ b/ee/app/services/ai/model_selection/update_service.rb @@ -27,6 +27,8 @@ def execute update_params[:model_definitions] = fetch_model_definition.payload if feature_setting.update(update_params) + record_audit_event + ServiceResponse.success(payload: feature_setting) else ServiceResponse.error(payload: feature_setting, @@ -37,6 +39,27 @@ def execute private attr_accessor :feature_setting, :user, :selection_scope, :params + + def record_audit_event + model = params[:offered_model_ref] + feature = feature_setting.feature + scope_type = selection_scope.class.name + scope_id = selection_scope.id + + audit_context = { + name: 'model_selection_feature_changed', + author: user, + scope: selection_scope, + target: selection_scope, + message: "The LLM #{model} has been selected for the feature #{feature} of #{scope_type} with ID #{scope_id}", + additional_details: { + model_ref: model, + feature: feature + } + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end end end end diff --git a/ee/spec/services/ai/model_selection/update_service_spec.rb b/ee/spec/services/ai/model_selection/update_service_spec.rb index 2d4ebbd42dff2b0bc8eac336f2d9b0ea3037bc03..705829db20e122f7f7075fdc436ee91efd87a995 100644 --- a/ee/spec/services/ai/model_selection/update_service_spec.rb +++ b/ee/spec/services/ai/model_selection/update_service_spec.rb @@ -4,7 +4,7 @@ RSpec.describe ::Ai::ModelSelection::UpdateService, feature_category: :"self-hosted_models" do let_it_be(:user) { create(:user) } - let(:feature_setting) { build(:ai_namespace_feature_setting, feature: :code_generations) } + let_it_be(:group) { create(:group) } let(:offered_model_ref) { 'openai_chatgpt_4o' } let(:params) { { offered_model_ref: offered_model_ref } } let(:model_definitions) do @@ -25,8 +25,32 @@ } end + let(:feature_setting) do + build(:ai_namespace_feature_setting, feature: :code_generations, namespace: group) + end + let(:model_definitions_response) { model_definitions.to_json } + let(:audit_event) do + feature = feature_setting.feature + selection_scope = feature_setting.model_selection_scope + scope_type = 'Group' + scope_id = selection_scope.id + + { + name: 'model_selection_feature_changed', + author: user, + scope: selection_scope, + target: selection_scope, + message: + "The LLM #{offered_model_ref} has been selected for the feature #{feature} of #{scope_type} with ID #{scope_id}", + additional_details: { + model_ref: offered_model_ref, + feature: feature + } + } + end + include_context 'with the model selections fetch definition service as side-effect' subject(:service) { described_class.new(feature_setting, user, params) } @@ -58,6 +82,8 @@ context 'when feature setting update is successful' do context 'when feature setting is new' do it 'returns a success response with the feature setting' do + expect(Gitlab::Audit::Auditor).to receive(:audit).with(audit_event).and_call_original + expect(feature_setting.persisted?).to be(false) response = service.execute @@ -77,6 +103,8 @@ end it 'returns a success response with the feature setting' do + expect(Gitlab::Audit::Auditor).to receive(:audit).with(audit_event).and_call_original + expect(feature_setting.offered_model_ref).not_to eq(offered_model_ref) expect(feature_setting.offered_model_name).not_to eq('OpenAI Chat GPT 4o') @@ -94,6 +122,8 @@ let(:offered_model_ref) { '' } it 'returns a success response with the feature setting' do + expect(Gitlab::Audit::Auditor).to receive(:audit).with(audit_event).and_call_original + expect(feature_setting.offered_model_ref).not_to eq(offered_model_ref) expect(feature_setting.offered_model_name).not_to eq('OpenAI Chat GPT 4o')