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.
AiContextScopeManagersorts 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 withsupportsSubscriptions() === 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 detectiontranslateItem()/translateItems()— centralized translation helpers
- Replaces the earlier detector/trait approach. Consumers now include:
AiContextSelectorAiContextScopeLanguageListAiContextItemsLoadAiContextItemById
- Detection order:
- Request path prefix
- Referer prefix for AJAX requests
- Drupal content-language negotiation
Selection cache metadata
- Adds
ai_context.detected_languagecache context so selection results vary correctly when language is inferred from the Referer header on unprefixed AJAX paths. AiContextSelectoruses this context instead of relying onlanguages:language_contentalone 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()returnsFALSEinstead ofNULLwhen the request is missing).
Items with no values for those scopes remain neutral/unrestricted.
Function call fixes
ListAiContextItemsseeds entity context viaAiContextEntityTargetResolver::setCurrentEntity()beforefilterByCurrentContext()whenentity_typeandentity_idare provided. This fixes bundle-scoped items being incorrectly excluded on admin or other routes without a current route entity.LoadAiContextItemByIdusesAiContextLanguageService::translateItems()so load-by-id translation follows the same Referer-aware detection as selection and listing.
Documentation
Updates:
docs/developers/scope_api.mddocs/developers/services.mddocs/developers/custom_scopes.md(positive custom-scope weight example)docs/features/scopes.mddocs/features/multilingual.md
Testing instructions
-
Clear caches after applying the branch:
ddev exec drush cr -
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.
-
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.
-
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_entitysubscription entry. - Confirm Target Entity subscription values do not change ranking for unrelated items.
-
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.
-
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.
-
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.
-
Verify list function call entity bundle filtering:
- Create published context items scoped to
node:articleandnode:page. - From an admin path (no route entity), call
ai_context_list_ai_context_itemswithentity_type=nodeandentity_idfor an article node. - Confirm the article-scoped item appears and the page-scoped item does not.
- Create published context items scoped to
-
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- 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 crafter 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