Add minimum coverage threshold (1/6): DB migration, model & feature flag
What does this MR do and why?
This is MR 1 of 6 in a stacked series that adds a minimum coverage threshold to the Coverage-Check approval rule, allowing teams to enforce an absolute minimum coverage percentage before merging. This is a rework of !224343 (closed), split into smaller MRs as discussed with @kherman1 and @hfyngvason.
This MR establishes the database foundation and model layer. Subsequent MRs build on top of this:
| MR | Branch | Base | Content |
|---|---|---|---|
| 1 (this MR) | 6284-coverage-threshold-1-db-model-ff |
master |
DB migration & model validation |
| 2 | 6284-coverage-threshold-2-service-rest |
6284-coverage-threshold-1-db-model-ff |
Service logic, REST API & feature flag |
| 3 | 6284-coverage-threshold-3-graphql |
6284-coverage-threshold-2-service-rest |
GraphQL mutations & fields |
| 4 | 6284-coverage-threshold-4-blockage-reason |
6284-coverage-threshold-3-graphql |
blockage_reason feature & GraphQL exposure |
| 5 | 6284-coverage-threshold-5-frontend |
6284-coverage-threshold-4-blockage-reason |
Frontend: Settings UI + MR widget feedback |
| 6 | 6284-coverage-threshold-6-docs |
master |
Documentation (standalone, no dependency) |
MRs 5 and 6 are reworks of the existing !224368 (closed) (Frontend) and !224375 (closed) (Docs).
Splitting was discussed and agreed upon with @kherman1 and @hfyngvason in !224343 (closed).
This MR includes:
- Two database migrations adding
coverage_minimum_threshold(float, nullable) toapproval_project_rulesandapproval_merge_request_rules(one migration per table to avoid multi-table locking) - A separate migration adding DB-level CHECK constraints (0-100 range) on both tables to ensure data integrity at the schema layer
- Model validation (0-100 range) on
ApprovalProjectRule, scoped tocode_coveragereport type, kept consistent with the DB CHECK constraint (no feature flag gating, per @SamWord's review) - Addition of
coverage_minimum_thresholdtoREPORT_APPROVER_ATTRIBUTESfor automatic propagation from project rules to MR rules
Note: The
coverage_minimum_thresholdfeature flag (gitlab_com_derisk) was originally introduced in this MR but moved to MR 2 (!226370 (merged)), where it is first consumed in the critical mergeability path. Splitting it off here keeps MR 1 free of unused FF definitions onmaster.
Files to review in this MR
db/migrate/20260425171811_add_coverage_minimum_threshold_to_approval_project_rules.rb- Migration (approval_project_rules)db/migrate/20260425171813_add_coverage_minimum_threshold_to_approval_merge_request_rules.rb- Migration (approval_merge_request_rules)db/migrate/20260425171817_add_coverage_minimum_threshold_check_constraints.rb- CHECK constraints migrationdb/schema_migrations/20260425171811db/schema_migrations/20260425171813db/schema_migrations/20260425171817db/structure.sql- Schema changes for both tablesee/app/models/approval_project_rule.rb-REPORT_APPROVER_ATTRIBUTES+ validationee/spec/models/approval_project_rule_spec.rb- Validation tests
Related to #6284 Extracted from !224343 (closed)
How to set up and validate locally
-
Run the migration
bin/rails db:migrate -
Verify in Rails Console
bin/rails console
# Verify the columns exist
ApprovalProjectRule.column_names.include?('coverage_minimum_threshold')
# => true
ApprovalMergeRequestRule.column_names.include?('coverage_minimum_threshold')
# => true
# Create a test rule with valid threshold
project = Project.first
rule = ApprovalProjectRule.new(
project: project,
name: 'Coverage-Check-Test',
rule_type: :report_approver,
report_type: :code_coverage,
approvals_required: 1,
coverage_minimum_threshold: 80.0
)
rule.valid?
# => true
# Test validation: value above 100 is rejected
rule.coverage_minimum_threshold = 150.0
rule.valid?
# => false
rule.errors[:coverage_minimum_threshold]
# => ["must be less than or equal to 100"]
# Test validation: value below 0 is rejected
rule.coverage_minimum_threshold = -5.0
rule.valid?
# => false
# Test validation: nil is allowed (feature is optional)
rule.coverage_minimum_threshold = nil
rule.valid?
# => true
# Validation is scoped to code_coverage rules only - other report types skip the check
non_coverage_rule = build(:approval_project_rule, :license_scanning, coverage_minimum_threshold: 150.0)
non_coverage_rule.valid?
# => true (validation skipped, value is semantically irrelevant for non-code_coverage rules;
# the DB CHECK constraint still enforces NULL or 0..100 at the schema layer)- Run the specs
bundle exec rspec ee/spec/models/approval_project_rule_spec.rb -e 'coverage_minimum_threshold' # Expected: 2 examples, 0 failures
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.
- The change is backwards compatible. When
coverage_minimum_thresholdisnil(default), behavior is unchanged. - Database migrations are safe: separate
add_columnper table withnull: true, no default, no index needed. CHECK constraints added in a dedicated migration. - Model validation is consistent with the DB CHECK constraint and applies regardless of feature flag state.
- Feature is behind the existing
coverage_check_approval_rulelicensed feature, no new licensing flag needed.
Related to #6284