Account for pending custom types when validating the Work Item Type limit
What does this MR do and why?
This MR prevents creation of an active, non-converted custom Work Item Type when its top-level organization or root group is already at the 40-type maximum.
The existing create validation checks only the persisted total. At exactly 40, the pending custom type is not yet represented in that count, so validation can pass and the active total can become 41.
This change validates the prospective active cardinality by accounting for the pending record's contribution.
Implementation
max_types_per_parent_limit now evaluates:
persisted total + additional active contribution > MAXThe pending contribution is:
1for an active, non-converted pending type.0for a converted or archived pending type.
A blanket total_types_count >= MAX_TYPE_PER_PARENT check would also reject converted or archived records that do not increase the active cardinality. Accounting for the pending contribution explicitly preserves those existing semantics without adding a query.
Before / After
Before:
40 + ordinary active custom type
→ validation passes
→ total becomes 41
After:
40 + ordinary active custom type
→ existing validation error
→ total remains 40Regression risk
Focused coverage verifies that:
- Below-limit ordinary creation still succeeds.
- An active, non-converted type is rejected at the exact limit.
- Converted types preserve their existing replacement semantics at the limit.
- Archived types do not consume active quota.
- Archiving an active type frees a slot.
- Existing unarchive validation remains unchanged.
- Organization and root-group namespace paths behave consistently.
The change introduces no additional SQL query or Provider lookup and does not alter existing concurrency or locking behavior.
References
Closes #628859 (closed)
Screenshots or screen recordings
Not applicable. This change has no UI impact.
How to set up and validate locally
bundle exec rspec ee/spec/models/work_items/types_framework/custom/type_spec.rb:337 ee/spec/models/work_items/types_framework/custom/type_spec.rb:365 --format documentation
bundle exec rspec ee/spec/services/work_items/types/create_service_spec.rb:59 --format documentation
bundle exec rspec ee/spec/models/work_items/types_framework/custom/type_spec.rb ee/spec/services/work_items/types/create_service_spec.rb
bundle exec rspec ee/spec/services/work_items/types/update_service_spec.rb
bundle exec rubocop ee/app/models/work_items/types_framework/custom/type.rb ee/spec/models/work_items/types_framework/custom/type_spec.rb ee/spec/services/work_items/types/create_service_spec.rb
git diff --checkResults:
- Final focused regression against the original comparator: 9 examples, 3 expected failures.
- The same regression with this patch: 9 examples, 0 failures.
- Exact model matrix: 8 examples, 0 failures.
- Exact CreateService regression: 1 example, 0 failures.
- Full custom type model and CreateService suites: 166 examples, 0 failures.
- Full UpdateService suite: 64 examples, 0 failures.
- RuboCop: 3 files inspected, no offenses.
git diff --check: passed.
Additional verification:
A focused GraphQL request-spec run was attempted, but the local test environment failed before mutation execution with LoadError: cannot load such file -- sass. No GraphQL spec is changed by this MR. Source tracing confirms that workItemTypeCreate delegates to the verified WorkItems::Types::CreateService path.
MR acceptance checklist
I evaluated this MR against the MR acceptance checklist, including focused regression coverage, relevant model and service specs, linting, query impact, and preservation of existing converted, archived, and unarchive behavior.
AI assistance
AI tooling was used to assist with investigation and drafting. The contributor independently reviewed the implementation, reproduced the bug locally, verified the regression test and fix, and takes responsibility for the submitted changes.