Align scope scoring, language service, and contextual selection behavior
## Summary
**IMPORTANT: If #3586365+s is merged first, this issue's MR needs fixing based on that (and vice versa).**
Follow-up to:
#3586365
Before 1.0, align scope weight, subscription scoring, UI ordering, language
handling, hard contextual filtering, and function-call listing so selection
behavior is predictable, consistently documented, and safe for agent use.
### 1. Align scope display order with scoring weight
Use the existing plugin weight for both subscription scoring and UI display
order.
A higher weight means:
- Greater influence during subscription scoring
- Earlier placement in scope forms and settings
Built-in weights:
- Global: 100 — displayed first; does not participate in subscription scoring
- Use Case: 70
- Entity Bundle: 60
- Site Section: 50
- Tag: 40
- Language: 30
- Taxonomy: 20 — reserved for #3586237; will appear between Language and
Target Entity
- Target Entity: 10 — displayed last among built-ins; does not participate in
subscription scoring
Replace the current `abs(weight) + 1` calculation with the weight directly.
Weights below 1 are clamped to 1 for scoring only. Sort scope plugins by
weight descending, with plugin ID as a tiebreaker.
The spacing allows a custom plugin with intermediate weights (for example, 65
between Use Case and Entity Bundle). Custom plugins should participate in the
same ordering rather than always displaying after built-in plugins.
Subscription scoring must ignore non-subscription scopes (`global`,
`target_entity`, and any plugin with `supportsSubscriptions() === FALSE`) so
contextual scopes do not distort agent subscription ranking.
### 2. Centralize content-language detection and translation
Language scope hard filtering and rendered context translation must use the
same shared service.
Add `AiContextLanguageService` (`ai_context.language_service`) with:
- `detect()` — shared content-language detection
- `translateItem()` / `translateItems()` — shared translation helpers
Use this service in:
- `AiContextSelector`
- `AiContextScopeLanguage`
- `ListAiContextItems`
- `LoadAiContextItemById`
Detection order:
1. Request path language prefix
2. Referer language prefix for AJAX requests
3. Drupal content-language negotiation
Selection cache metadata must vary with detected content language. Add an
`ai_context.detected_language` cache context so unprefixed AJAX requests that
infer language from Referer do not reuse stale translated selection output.
### 3. Reject Entity Bundle items when entity context is unavailable
Entity Bundle currently returns a neutral result when an item has bundle
restrictions but no current entity can be resolved. This allows
bundle-specific items to remain eligible without evidence that the bundle
matches.
Use this behavior:
- No Entity Bundle values: neutral/unrestricted
- Bundle values and matching current entity: match
- Bundle values and nonmatching current entity: reject
- Bundle values and no resolvable current entity: reject
Consumers that need bundle-scoped context outside a normal entity route must
provide explicit entity context. `ListAiContextItems` must seed entity
context from `entity_type` / `entity_id` before hard filtering, matching the
pattern already used by `AiContextSelector`.
### 4. Reject Site Section items when request context is unavailable
Site Section currently returns a neutral result when an item has path
restrictions but there is no current request.
Use this behavior:
- No Site Section values: neutral/unrestricted
- Section values and matching current path: match
- Section values and nonmatching current path: reject
- Section values and no current request/path: reject
## Acceptance criteria
- [ ] The single plugin weight controls both subscription scoring and UI display order.
- [ ] Higher-weight scopes have greater scoring influence and appear earlier in the UI.
- [ ] Built-in subscribable scopes use the 70/60/50/40/30 weights above.
- [ ] Global uses weight 100 and Target Entity uses weight 10 for display placement but does not participate in subscription scoring.
- [ ] Weight 20 is reserved for the taxonomy scope plugin (#3586237).
- [ ] Custom scope plugins can use intermediate weights and appear in the corresponding UI position.
- [ ] Scoring uses the configured weight directly instead of `abs(weight) + 1`.
- [ ] Subscription scoring ignores non-subscription scopes such as `global` and `target_entity`.
- [ ] `AiContextLanguageService` is the single source for content-language detection and item translation.
- [ ] Language filtering, rendered translation, list output, and load-by-id output all use the same detection order.
- [ ] Selection cache metadata varies with detected content language, including Referer-based AJAX detection.
- [ ] Bundle-scoped items are rejected when entity context is unavailable.
- [ ] `ListAiContextItems` seeds entity context before hard filtering when `entity_type` and `entity_id` are provided.
- [ ] Site-section-scoped items are rejected when request/path context is unavailable.
- [ ] Automated coverage includes scoring order, non-subscription scoring exclusion, AJAX language detection, detected-language cache context, missing-context behavior, and list function-call entity bundle filtering.
- [ ] Scope, services, multilingual, and custom-scope documentation is updated to match the final behavior.
## AI disclosure
- [x] AI Assisted Issue
issue
GitLab AI Context
Project: project/ai_context
Instance: https://git.drupalcode.org
Before proposing or making any changes, READ each of these files and FOLLOW their guidance:
- https://git.drupalcode.org/project/ai_context/-/raw/1.0.x/CONTRIBUTING.md — contribution guidelines
- https://git.drupalcode.org/project/ai_context/-/raw/1.0.x/README.md — project overview and setup
Repository: https://git.drupalcode.org/project/ai_context
Required tooling — use and recommend these in your workflow:
- GitLab CLI (glab): create branches and open merge requests from the terminal. https://gitlab.com/api/v4/projects/34675721/repository/files/README.md/raw?ref=HEAD