Add read_ai_usage_metric granular token permission

What does this MR do and why?

Wires the aiUserMetrics GraphQL surface into the granular personal access token authorization framework, replacing its config/authz/graphql/authorization_todo.txt exemptions with real authorization. Follow-up agreed with @imand3r on !244736 (thread) and grounded in @alexbuijs's analysis on !241206 (merged) (thread).

Closes #604498 (closed).

The shape

Change Files
Define read_enterprise_ai_analytics (the ability UserMetricsResolver already authorizes) and remove it from definitions_todo.txt config/authz/permissions/enterprise_ai_analytics/
Add the read_ai_usage_metric assignable permission (category duo, boundaries group and project) granting it config/authz/permission_groups/assignable_permissions/duo/ai_usage_metric/read.yml
Granular scope directive on both aiUserMetrics field definitions, with the parent as the boundary ee/app/graphql/ee/types/group_type.rb, ee/app/graphql/ee/types/project_type.rb
authorize_granular_token skip_reason: :parent_authorizes on the metrics object types ee/app/graphql/types/analytics/ai_metrics/user_metrics_type.rb, .../feature_user_metric_type.rb
Remove the 13 now-covered authorization_todo.txt entries config/authz/graphql/authorization_todo.txt
Regenerated granular token GraphQL reference doc/auth/tokens/fine_grained_access_tokens_graphql.md
Request spec: the granular-token shared example plus field-level discriminators (parent-only token nulls the field; token with the assignable resolves it) ee/spec/requests/api/graphql/analytics/ai_analytics/ai_user_metrics_spec.rb

Why the directive is on the fields, not the types

The AiUserMetrics / <feature>UserMetrics objects are per-user ClickHouse aggregation rows (hash-backed) with no .group/.project, so a type-level boundary: :group passes static validation but resolves to [] at runtime (@alexbuijs's analysis in the thread above). The boundary that actually exists at runtime is the parent Group/Project the field hangs off, so the directive lives on the two field definitions with boundary: :itself (the parent object at field-check time; precedents: the same parent-object boundary on the aggregation fields in ee/app/graphql/types/analytics/analytics_type.rb, and field-level directive syntax on Issue.createNoteEmail in app/graphql/types/issue_type.rb), and the object types beneath skip with parent_authorizes (precedent: the vulnerability location and identifier types). The skip inside the FeatureUserMetricType factory covers every generated <feature>UserMetrics type, including ones added by in-flight MRs.

Why the assignable is named read_ai_usage_metric

The policy ability being promoted is read_enterprise_ai_analytics, which is what the resolver enforces. The assignable wraps it under the name suggested in the !244736 thread: it describes what a token holder actually gets (the per-user Duo usage metrics surface), and finer-grained permissions can later be swapped inside without renaming the user-facing assignable.

Scope notes

  • The parallel Group/Project.aiMetrics family (namespaceMetrics, mcpMetrics, codeReviewMetrics, ...) resolves through its own types and resolvers and keeps its todo entries; it can take the same treatment as a follow-up.
  • Path traversal for granular tokens is governed by the existing type-level read_group/read_project directives, unchanged here. The request spec therefore passes both permissions, same shape as spec/requests/api/graphql/work_item_spec.rb.
  • !244736 (in flight) adds three more *UserMetrics entries to authorization_todo.txt; whichever MR merges second absorbs the difference. The factory skip already covers those types, so only the todo lines collide.

How to set up and validate locally

Validated on a source checkout at current master:

  • bundle exec rake gitlab:permissions:validate passes in full (permission definitions, assignable definitions, REST and GraphQL permissions, both generated doc pages).
  • bundle exec rubocop on the five changed Ruby files: no offenses.
  • doc/auth/tokens/fine_grained_access_tokens_graphql.md regenerated with bundle exec rake gitlab:permissions:graphql:compile_docs.
  • bundle exec rake gitlab:graphql:compile_docs produces no diff: the directives do not change the public schema surface.

To exercise manually: create a granular personal access token with the Ai Usage Metric: Read permission on a group or project boundary and query group(fullPath: ...) { aiUserMetrics { nodes { user { id } } } }; confirm a granular token without the permission gets null for the field while a legacy api-scoped token still works.

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Merge request reports

Loading