Implement tenantChainCache with 60-second TTL to amortize DB reads for
per-tenant provider chain resolution. Cache is TTL-based with RWMutex
protection and event-driven invalidation on config_secrets changes.
Chain resolution rules:
- Parse tenant settings from builtin_tool_tenant_configs
- Apply NormalizeWebSearchProviderOrder for user-specified order
- Append DDG last (force-enabled, no API key required)
- Skip disabled providers or providers without API keys
Include comprehensive unit tests for cache hit/miss, expiry, and
concurrent access patterns.
Refactor web_search Execute to resolve its provider chain per-request
via BuiltinToolSettingsFromCtx instead of iterating the singleton's
hardcoded provider list. Tenant admins can now reorder or disable
providers via builtin_tool_tenant_configs.settings using the shape
{"provider_order":[...], "brave":{"enabled":false}, ...}. Unknown
provider names in provider_order are logged + skipped — no injection
path via this settings blob. Malformed JSON falls back to defaults
(fail-open so a bad admin paste never crashes the tool). Secrets stay
in config_secrets — tenant cannot inject API keys through this path.
11 unit tests cover reorder, filter, disable, unknown-skip, malformed,
global-layer, and tenant-vs-global precedence.
Note: This is the MVP tenant adoption half of the plan's Phase 7. The
Exa + Tavily feature port from PR 825 and coordination with its author
is deferred to a separate follow-up (the port is net-new feature work
orthogonal to the overlay adoption).