Prevent archived labels from being newly assigned to issuables

What does this MR do and why?

This MR prevents archived labels from becoming new associations at the shared issuable label-assignment boundary.

Archived labels remain available for historical associations, search, and removal. Before this change, Issue REST updates could resolve an archived project or inherited group label through LabelsFinder and persist it as a new association.

This conflicts with the existing archived-label behavior in the UI and quick actions, where archived labels are no longer assignable.

The Issue REST behavior is covered directly, with focused coverage for the shared title- and ID-based assignment paths.

Before

Issue does not have "legacy"
"legacy" is archived

PUT /projects/:id/issues/:issue_iid
add_labels=legacy

=> "legacy" becomes attached

After

The update remains successful, but the archived label is not newly attached.

Existing archived associations are not removed merely because the label is archived. Existing replacement, scoped-label, locked-label, and explicit-removal semantics continue to apply normally.

Implementation

Labels::AvailableLabelsService records which IDs are active while performing the existing authorized label resolution:

  • ID resolution retrieves id and archived from the existing available-label relation.
  • Title resolution records non-archived IDs from the Label objects it already resolves.
  • Newly created labels keep the existing creation behavior and are recorded as active.

Issuable::Callbacks::Labels then distinguishes genuinely new label IDs from IDs already associated with the issuable. Only active resolved IDs can become new associations, while existing IDs remain eligible.

The implementation deliberately does not add archived: false to the title lookup before Labels::FindOrCreateService. Archived title matches must still resolve as existing labels; hiding them could make an existing archived title appear missing and trigger the existing label-creation path.

Global LabelsFinder behavior therefore remains unchanged.

Behavioral coverage

The regression coverage verifies:

  • active and archived project-label assignment by title and ID;
  • active and archived inherited group labels;
  • active labels combined with archived or invalid label titles;
  • labels= replacement;
  • add_label_ids, label_ids, and remove_label_ids;
  • preservation of an existing archived association when adding an unrelated active label;
  • explicit archived-label removal;
  • create-path label candidates;
  • the interaction with the work item label limit;
  • merged-MR locked-label behavior;
  • EE scoped-label behavior;
  • no resource-label event for a rejected archived addition;
  • existing missing-title creation behavior.

Query performance

The final implementation reuses archive state from the existing authorized label resolution and does not introduce an additional SQL query.

Path Issuables Baseline Final Increment
IDs 1 52 52 0
IDs 20 774 774 0
Titles 1 41 41 0
Titles 20 782 782 0

The existing ID-resolution query selects one additional boolean column (archived). Focused query specs also verify that filtering the recorded IDs performs no query.

Edge cases and scope

Same-title project and inherited group labels already have order-dependent resolution through the existing title resolver. This MR does not change which matching label that resolver selects. If the resolver selects an archived label, the new-assignment check rejects that selection. Redesigning same-title precedence is outside the scope of this fix.

For scoped labels, a new active label replaces an existing archived label in the same scope, while an active label from another scope preserves the existing archived association. A newly requested archived scoped label is rejected.

For locked labels on merged merge requests, a newly requested archived locked label is rejected, an existing archived locked label remains while an active label is added, and an attempted removal continues to restore the locked label.

Source inspection confirms that Work Item move/clone data sync, project import/export, and GitHub import restore or insert LabelLink records through separate historical-copy paths rather than this interactive assignment filter.

The request regression verifies that a rejected archived addition does not create a resource-label event. Existing system-note generation continues to consume the resulting association changes.

A label could theoretically be archived concurrently after application-level resolution and before persistence. This pre-existing check-then-persist race is outside the scope of this fix.

References

Closes #628860 (closed)

Screenshots or screen recordings

Not applicable. This is a backend-only behavior change with no visible UI changes.

How to set up and validate locally

Run the focused CE/FOSS specs:

bundle exec rspec \
  spec/requests/api/issues/put_projects_issues_spec.rb \
  spec/services/issuable/callbacks/labels_spec.rb \
  spec/services/labels/available_labels_service_spec.rb

Result: 100 examples, 0 failures.

Run the same coverage with the EE extension:

FOSS_ONLY=0 bundle exec rspec \
  spec/requests/api/issues/put_projects_issues_spec.rb \
  spec/services/issuable/callbacks/labels_spec.rb \
  spec/services/labels/available_labels_service_spec.rb \
  ee/spec/services/ee/issuable/callbacks/labels_spec.rb

Result: 103 examples, 0 failures.

Run the focused query regression examples:

bundle exec rspec spec/services/labels/available_labels_service_spec.rb:116

Result: 3 examples, 0 failures.

Run RuboCop for all changed files:

bundle exec rubocop \
  app/services/issuable/callbacks/labels.rb \
  app/services/labels/available_labels_service.rb \
  spec/requests/api/issues/put_projects_issues_spec.rb \
  spec/services/labels/available_labels_service_spec.rb \
  spec/services/issuable/callbacks/labels_spec.rb \
  ee/spec/services/ee/issuable/callbacks/labels_spec.rb

Result: 6 files inspected, no offenses detected.

The reviewer-requested active-plus-archived, active-plus-invalid-title, and label-limit interaction examples were also run against the pre-fix implementation and failed for the expected reasons.

MR acceptance checklist

  • The regression coverage fails against the pre-fix implementation and passes with this change.
  • CE and EE behavior, REST behavior, authorization boundaries, and query counts were reviewed.
  • Existing documentation already describes archived labels as unavailable for new selection while preserving existing associations.
  • No migration, schema change, new query, feature flag, or UI change is introduced.
  • The final diff, commit message, changelog metadata, and repository-local style checks were reviewed.

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.

Edited by Whale Tech

Merge request reports

Loading
Loading