Add guardrail spec for EE API route tiers

What does this MR do and why?

Adds a spec that fails when an EE API route in the OpenAPI spec has no route_setting :tier, so new paid endpoints can't ship without x-gitlab-tier. This is the last MR for the tier backfill in #631956 (closed). The other seven are merged.

  • ee/spec/lib/api/every_ee_api_endpoint_tier_spec.rb: picks routes the same way the OpenAPI v3 generator does, then keeps the ones defined under ee/. The spec has two examples:

    1. Every route declares a tier, or is on the allowlist.
    2. No allowlist entry is stale, meaning the route now has a tier or no longer exists.
  • ee/spec/support/api/routes_without_tier.yml: an allowlist of the 125 EE routes without a tier, grouped by reason:

    Reason Routes
    Free on every tier (license, billing, seats, setup) 38
    Free on every tier (Orbit skills and status, which skip the entitlement check) 4
    Pending: gated by a GitLab Duo add-on rather than a tier 43
    Pending: instance admin endpoints that check only admin access 10
    Pending: likely Premium, with no license check of their own 12
    Pending: docs and code disagree, or the owning team needs to confirm 5
    Pending: deprecated managed_licenses endpoint 5
    Geo node-to-node traffic, candidates for excluded_api_classes 8

    Closing a pending group means annotating its routes and deleting their entries. The stale-entry example makes sure the list shrinks when that happens.

  • rubocop/cop/api/route_setting_tier.rb: docstring only. The cop validates tier values, but it can't detect a missing tier, so it now points to the new spec.

  • tests.yml and scripts/verify-tff-mapping: map EE API files and the allowlist to the new spec, so predictive MR pipelines run it when a route changes. Without the mapping, an untiered route would first fail on master.

Scope and design notes
  • Only routes whose source is under ee/. EE routes produced by shared CE loops (for example epic notes and group hooks) have their source in lib/. The route tier specs added in the shared-loops MR cover them.
  • No check that one definition site produces a single tier. The original plan included one, but it would fail on the shared CE loops, which produce Free and paid routes from one site on purpose.
  • Route selection matches lib/tasks/gitlab/openapi/v3_document.rb: API::Base.descendants, minus excluded_api_classes, minus hidden routes and the * catch-all. The spec checks exactly the routes the published spec contains.
  • A spec rather than a RuboCop cop with a todo file. RuboCop todos exclude whole files, and many files mix Free and paid routes, so new paid routes in those files would go unchecked. A cop also cannot see excluded_api_classes or tiers set through shared CE loops. The allowlist works per route, and records permanently Free routes with a reason. It follows spec/lib/api/every_api_endpoint_spec.rb and spec/models/every_model_spec.rb.
  • The spec takes about 10 seconds locally.

References

Screenshots or screen recordings

Not applicable: no UI change.

How to set up and validate locally

No manual steps. The change is a spec, and CI runs it. To see the failure message, remove a route_setting :tier from any EE route, for example in ee/lib/api/epics.rb.

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.

Edited by Vlad Wolanyk

Merge request reports

Loading
Loading