Issue #3586388: Align scope scoring and contextual detection.

Description

Aligns scope plugin weight, subscription scoring, UI display order, language detection, contextual hard filtering, and function-call listing so selection behavior matches the documented model from #3586365.

Scope weight and scoring

  • Built-in weights are now Global 100, Use Case 70, Entity Bundle 60, Site Section 50, Tag 40, Language 30, and Target Entity 10.
  • Weight 20 is reserved for the planned taxonomy scope plugin (#3586237), which will appear between Language and Target Entity.
  • Higher weight means earlier UI placement and greater subscription scoring influence.
  • AiContextScopeManager sorts all scope plugins by weight descending, with plugin ID as a tiebreaker. Custom scopes can appear between built-in scopes when given intermediate weights.
  • AiContextScopeResolver::calculateScopeScore() now uses plugin weight directly (weights below 1 are clamped to 1 for scoring only).
  • Subscription scoring skips non-subscription scopes (global, target_entity, and any plugin with supportsSubscriptions() === FALSE). This prevents target-entity or other contextual scopes from distorting agent subscription ranking.

Shared content-language service

  • Adds AiContextLanguageService (ai_context.language_service) with:
    • detect() — shared content-language detection
    • translateItem() / translateItems() — centralized translation helpers
  • Replaces the earlier detector/trait approach. Consumers now include:
    • AiContextSelector
    • AiContextScopeLanguage
    • ListAiContextItems
    • LoadAiContextItemById
  • Detection order:
    1. Request path prefix
    2. Referer prefix for AJAX requests
    3. Drupal content-language negotiation

Selection cache metadata

  • Adds ai_context.detected_language cache context so selection results vary correctly when language is inferred from the Referer header on unprefixed AJAX paths.
  • AiContextSelector uses this context instead of relying on languages:language_content alone for translated output.
  • The cache context value is the detected language code; Referer is read when computing that value. A generic headers cache context is not needed.

Fail-closed contextual scopes

  • Entity Bundle: items with bundle values are rejected when no current entity bundle can be resolved.
  • Site Section: items with section values are rejected when no current request/path is available (matchesCurrentContext() returns FALSE instead of NULL when the request is missing).

Items with no values for those scopes remain neutral/unrestricted.

Function call fixes

  • ListAiContextItems seeds entity context via AiContextEntityTargetResolver::setCurrentEntity() before filterByCurrentContext() when entity_type and entity_id are provided. This fixes bundle-scoped items being incorrectly excluded on admin or other routes without a current route entity.
  • LoadAiContextItemById uses AiContextLanguageService::translateItems() so load-by-id translation follows the same Referer-aware detection as selection and listing.

Documentation

Updates:

  • docs/developers/scope_api.md
  • docs/developers/services.md
  • docs/developers/custom_scopes.md (positive custom-scope weight example)
  • docs/features/scopes.md
  • docs/features/multilingual.md

Testing instructions

  1. Clear caches after applying the branch: ddev exec drush cr

  2. Verify scope UI order on a context item edit form and agent subscription form. Expected order: Global, Use Case, Entity Bundle, Site Section, Tag, Language, Target Entity.

  3. Verify subscription scoring influence:

    • Create two published context items with different Use Case values.
    • Configure an agent subscribed to one use case.
    • Confirm the matching item ranks above the nonmatching item during selection.
  4. Verify non-subscription scopes do not affect subscription scoring:

    • Create a context item with a Target Entity scope value.
    • Configure an agent with a target_entity subscription entry.
    • Confirm Target Entity subscription values do not change ranking for unrelated items.
  5. Verify shared language detection:

    • Configure at least two languages with URL prefixes.
    • Create a French context item and an English context item.
    • From a language-prefixed page, trigger an AJAX selection request whose path has no prefix but whose Referer does.
    • Confirm Language scope filtering and rendered translation both use the prefixed language.
  6. Verify fail-closed Entity Bundle behavior:

    • Create a context item scoped to a specific bundle.
    • Run selection with no current entity context.
    • Confirm the bundle-scoped item is excluded.
  7. Verify fail-closed Site Section behavior:

    • Create a context item scoped to a site section/path pattern.
    • Run selection with no current request/path.
    • Confirm the site-section-scoped item is excluded.
  8. Verify list function call entity bundle filtering:

    • Create published context items scoped to node:article and node:page.
    • From an admin path (no route entity), call ai_context_list_ai_context_items with entity_type=node and entity_id for an article node.
    • Confirm the article-scoped item appears and the page-scoped item does not.
  9. Run automated coverage:

cd web/modules/contrib/ai_context
./lint.sh

ddev phpunit \
  web/modules/contrib/ai_context/tests/src/Unit/AiContextScopeScoreCalculationTest.php \
  web/modules/contrib/ai_context/tests/src/Unit/AiContextLanguageServiceTest.php \
  web/modules/contrib/ai_context/tests/src/Kernel/AiContextScopeManagerTest.php \
  web/modules/contrib/ai_context/tests/src/Kernel/AiContextScopeResolverTest.php \
  web/modules/contrib/ai_context/tests/src/Kernel/AiContextScopeEntityBundleTest.php \
  web/modules/contrib/ai_context/tests/src/Kernel/AiContextScopeLanguageTest.php \
  web/modules/contrib/ai_context/tests/src/Kernel/AiContextSelectorTranslationTest.php \
  web/modules/contrib/ai_context/tests/src/Kernel/AiContextRequestFactoryResultTest.php \
  web/modules/contrib/ai_context/tests/src/Kernel/ListAiContextItemsTranslationTest.php \
  web/modules/contrib/ai_context/tests/src/Kernel/ListAiContextItemsEntityBundleTest.php
  1. Smoke-test admin pages after cache rebuild:
    • /admin/config/ai/context/items
    • /admin/config/ai/context/settings/general
    • One context item view page

Checklist

  • I have updated the MR title to use format: Issue #1234: My issue title.
  • I have updated the MR description to include: Closes #1234
  • I have performed a self-review of my own code
  • I have added or updated tests, or explained in the description why this change is not covered by tests
  • I have updated documentation for any new or changed functionality
  • I have written testing instructions and verified them locally
  • I have noted any required post-merge steps (config imports, cache rebuilds, manual changes)
  • This MR contains no breaking API or hook changes, or they are explicitly documented in the description

Post-merge steps

  • Run drush cr after deployment.

AI Compliance

Note

Check the one that best describes your usage, or leave all unchecked if AI was not significantly used.

  • AI Assisted Code
    Mainly written by a human; AI used for autocomplete or partial generation under full human supervision.

  • AI Generated Code
    Mainly generated by AI, reviewed and approved by a human before this MR was created.

  • Vibe Coded
    Generated by AI and only functionally reviewed before this MR was created.

Closes #3586388

Edited by Kristen Pol

Merge request reports

Loading