[1/5] VSA: Merge Elastic.Documentation.Api.* into single project#3333
Conversation
Three projects (Api.Core, Api.Infrastructure, Api.App) collapsed into Elastic.Documentation.Api. Mcp.Remote no longer references any Api.* project — its only deps are ServiceDefaults, services/Search, and services/Assembler. Key moves: - EuidSpanProcessor + EuidLogProcessor → ServiceDefaults (shared) - AddEuidEnrichment helper added to ServiceDefaults - Search gateway interfaces (IFullSearchGateway, INavigationSearchGateway, IChangesGateway) → services/Elastic.Documentation.Search - McpToolSourceName constant inlined into Mcp.Remote Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR consolidates the documentation API from a three-project structure (Core, Infrastructure, App) into a single unified 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/api/Elastic.Documentation.Api/Telemetry/OtlpProxyOptions.cs (1)
35-45:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFix environment variable key mismatch in
ResolveEndpoint().The documentation at lines 19–20 specifies
OTEL_EXPORTER_OTLP_ENDPOINTas priority 2, but the implementation at line 36 readsOTLP_PROXY_ENDPOINTinstead. The rest of the codebase (8+ locations including integration tests) usesOTEL_EXPORTER_OTLP_ENDPOINT, so following the documented contract will silently fail and fall back tolocalhost:4318.Change line 36 to read
OTEL_EXPORTER_OTLP_ENDPOINT, or if backward compatibility withOTLP_PROXY_ENDPOINTis needed, check both keys in priority order.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/api/Elastic.Documentation.Api/Telemetry/OtlpProxyOptions.cs` around lines 35 - 45, The ResolveEndpoint() logic uses the wrong environment variable key; change the envVarKey used in OtlpProxyOptions.ResolveEndpoint (currently set to "OTLP_PROXY_ENDPOINT") to "OTEL_EXPORTER_OTLP_ENDPOINT" or implement a fallback that checks both "OTEL_EXPORTER_OTLP_ENDPOINT" first then "OTLP_PROXY_ENDPOINT" to preserve backward compatibility, and ensure the Environment.GetEnvironmentVariable calls use that corrected key(s) so the method returns the documented OTEL_EXPORTER_OTLP_ENDPOINT value instead of falling back to the default.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/api/Elastic.Documentation.Api/Dockerfile`:
- Around line 34-39: The Dockerfile hardcodes linux-x64 in the publish RID and
the COPY path; change the publish and copy to use the computed runtime
identifier instead of "linux-x64". Replace the RUN dotnet publish invocation to
use a derived RID variable (e.g. use build args TARGETOS/TARGETARCH to form RID
like "${TARGETOS}-${TARGETARCH}" and pass -r ${RID}) and update the COPY
--from=base target path from release_linux-x64 to release_${RID} (ensure the
same RID logic is used in both the RUN dotnet publish and the COPY --from=base
lines so arm64 builds publish and copy the matching artifact).
In `@src/api/Elastic.Documentation.Api/OpenTelemetry/OpenTelemetryExtensions.cs`:
- Around line 85-87: The ServiceDefaults.Telemetry.TelemetryConstants qualifier
may be unresolved; update the two uses in OpenTelemetryExtensions.cs (the calls
to activity.SetTag and activity.AddBaggage referencing
ServiceDefaults.Telemetry.TelemetryConstants.UserEuidAttributeName) so the
symbol resolves: either add the correct using/using alias that imports the
namespace which declares ServiceDefaults (or a global using alias if project
style requires) or fully qualify ServiceDefaults with its declaring namespace
where ServiceDefaults is defined; ensure both references (UserEuidAttributeName
in activity.SetTag and activity.AddBaggage) are changed consistently.
In `@src/api/Elastic.Documentation.Mcp.Remote/Program.cs`:
- Line 27: The tracer provider is not configured to capture MCP activities
because AddEuidEnrichment() does not register the
"Elastic.Documentation.Api.McpTools" ActivitySource; update the tracer
configuration where builder.AddEuidEnrichment() is called (or the
TracerProviderBuilder helper) to also call
AddSource("Elastic.Documentation.Api.McpTools") so the TracerProvider will
receive MCP activities from that ActivitySource.
In
`@src/Elastic.Documentation.ServiceDefaults/Telemetry/EuidEnrichmentExtensions.cs`:
- Around line 83-84: Replace the use of Assembly.GetCallingAssembly() when
building the informationalVersion in EuidEnrichmentExtensions (the variable
named informationalVersion) with Assembly.GetEntryAssembly() so telemetry
`service.version` reflects the host service's assembly instead of the shared
library; if you want to be defensive, call Assembly.GetEntryAssembly() first and
fall back to Assembly.GetCallingAssembly() only if EntryAssembly is null, then
continue to read the AssemblyInformationalVersionAttribute from that chosen
assembly.
---
Outside diff comments:
In `@src/api/Elastic.Documentation.Api/Telemetry/OtlpProxyOptions.cs`:
- Around line 35-45: The ResolveEndpoint() logic uses the wrong environment
variable key; change the envVarKey used in OtlpProxyOptions.ResolveEndpoint
(currently set to "OTLP_PROXY_ENDPOINT") to "OTEL_EXPORTER_OTLP_ENDPOINT" or
implement a fallback that checks both "OTEL_EXPORTER_OTLP_ENDPOINT" first then
"OTLP_PROXY_ENDPOINT" to preserve backward compatibility, and ensure the
Environment.GetEnvironmentVariable calls use that corrected key(s) so the method
returns the documented OTEL_EXPORTER_OTLP_ENDPOINT value instead of falling back
to the default.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: bb359497-c6ad-4e03-b509-e98bc2ca8e81
📒 Files selected for processing (92)
aspire/AppHost.csaspire/aspire.csprojdocs-builder.slnxsrc/Elastic.Documentation.ServiceDefaults/Elastic.Documentation.ServiceDefaults.csprojsrc/Elastic.Documentation.ServiceDefaults/Logging/EuidLogProcessor.cssrc/Elastic.Documentation.ServiceDefaults/Telemetry/EuidEnrichmentExtensions.cssrc/Elastic.Documentation.ServiceDefaults/Telemetry/EuidSpanProcessor.cssrc/Elastic.Documentation.ServiceDefaults/Telemetry/TelemetryConstants.cssrc/api/Elastic.Documentation.Api.App/Elastic.Documentation.Api.App.csprojsrc/api/Elastic.Documentation.Api.Core/Elastic.Documentation.Api.Core.csprojsrc/api/Elastic.Documentation.Api.Infrastructure/Elastic.Documentation.Api.Infrastructure.csprojsrc/api/Elastic.Documentation.Api/Adapters/AskAi/AgentBuilderAskAiGateway.cssrc/api/Elastic.Documentation.Api/Adapters/AskAi/AgentBuilderStreamTransformer.cssrc/api/Elastic.Documentation.Api/Adapters/AskAi/AskAiGatewayFactory.cssrc/api/Elastic.Documentation.Api/Adapters/AskAi/AskAiProviderResolver.cssrc/api/Elastic.Documentation.Api/Adapters/AskAi/ElasticsearchAskAiMessageFeedbackGateway.cssrc/api/Elastic.Documentation.Api/Adapters/AskAi/KibanaOptions.cssrc/api/Elastic.Documentation.Api/Adapters/AskAi/LlmGatewayAskAiGateway.cssrc/api/Elastic.Documentation.Api/Adapters/AskAi/LlmGatewayOptions.cssrc/api/Elastic.Documentation.Api/Adapters/AskAi/LlmGatewayStreamTransformer.cssrc/api/Elastic.Documentation.Api/Adapters/AskAi/SseParser.cssrc/api/Elastic.Documentation.Api/Adapters/AskAi/StreamTransformerBase.cssrc/api/Elastic.Documentation.Api/Adapters/AskAi/StreamTransformerFactory.cssrc/api/Elastic.Documentation.Api/Adapters/Telemetry/AdotOtlpGateway.cssrc/api/Elastic.Documentation.Api/AskAi/AskAiEvent.cssrc/api/Elastic.Documentation.Api/AskAi/AskAiMessageFeedbackRequest.cssrc/api/Elastic.Documentation.Api/AskAi/AskAiMessageFeedbackUsecase.cssrc/api/Elastic.Documentation.Api/AskAi/AskAiUsecase.cssrc/api/Elastic.Documentation.Api/AskAi/IAskAiGateway.cssrc/api/Elastic.Documentation.Api/AskAi/IAskAiMessageFeedbackGateway.cssrc/api/Elastic.Documentation.Api/AskAi/IStreamTransformer.cssrc/api/Elastic.Documentation.Api/Aws/IParameterProvider.cssrc/api/Elastic.Documentation.Api/Aws/LambdaExtensionParameterProvider.cssrc/api/Elastic.Documentation.Api/Aws/LocalParameterProvider.cssrc/api/Elastic.Documentation.Api/Caching/CacheKey.cssrc/api/Elastic.Documentation.Api/Caching/DynamoDbDistributedCache.cssrc/api/Elastic.Documentation.Api/Caching/IDistributedCache.cssrc/api/Elastic.Documentation.Api/Caching/InMemoryDistributedCache.cssrc/api/Elastic.Documentation.Api/Caching/MultiLayerCache.cssrc/api/Elastic.Documentation.Api/Changes/ChangesUsecase.cssrc/api/Elastic.Documentation.Api/Dockerfilesrc/api/Elastic.Documentation.Api/Elastic.Documentation.Api.csprojsrc/api/Elastic.Documentation.Api/Gcp/GcpIdTokenProvider.cssrc/api/Elastic.Documentation.Api/Gcp/IGcpIdTokenProvider.cssrc/api/Elastic.Documentation.Api/MappingsExtensions.cssrc/api/Elastic.Documentation.Api/OpenTelemetry/OpenTelemetryExtensions.cssrc/api/Elastic.Documentation.Api/Program.cssrc/api/Elastic.Documentation.Api/Properties/launchSettings.jsonsrc/api/Elastic.Documentation.Api/Search/FullSearchUsecase.cssrc/api/Elastic.Documentation.Api/Search/NavigationSearchUsecase.cssrc/api/Elastic.Documentation.Api/SerializationContext.cssrc/api/Elastic.Documentation.Api/ServicesExtension.cssrc/api/Elastic.Documentation.Api/Telemetry/IOtlpGateway.cssrc/api/Elastic.Documentation.Api/Telemetry/OtlpForwardResult.cssrc/api/Elastic.Documentation.Api/Telemetry/OtlpProxyOptions.cssrc/api/Elastic.Documentation.Api/Telemetry/OtlpProxyRequest.cssrc/api/Elastic.Documentation.Api/Telemetry/OtlpProxyUsecase.cssrc/api/Elastic.Documentation.Api/TelemetryConstants.cssrc/api/Elastic.Documentation.Api/Validation/IValidator.cssrc/api/Elastic.Documentation.Api/appsettings.Development.jsonsrc/api/Elastic.Documentation.Api/appsettings.edge.jsonsrc/api/Elastic.Documentation.Api/appsettings.jsonsrc/api/Elastic.Documentation.Mcp.Remote/Elastic.Documentation.Mcp.Remote.csprojsrc/api/Elastic.Documentation.Mcp.Remote/Program.cssrc/api/Elastic.Documentation.Mcp.Remote/Telemetry/McpToolTelemetry.cssrc/api/Elastic.Documentation.Mcp.Remote/Tools/CoherenceTools.cssrc/api/Elastic.Documentation.Mcp.Remote/Tools/SearchTools.cssrc/services/Elastic.Documentation.Search/ChangesGateway.cssrc/services/Elastic.Documentation.Search/Elastic.Documentation.Search.csprojsrc/services/Elastic.Documentation.Search/FullSearchGateway.cssrc/services/Elastic.Documentation.Search/IChangesGateway.cssrc/services/Elastic.Documentation.Search/IFullSearchGateway.cssrc/services/Elastic.Documentation.Search/INavigationSearchGateway.cssrc/services/Elastic.Documentation.Search/MockSearchGateway.cssrc/services/Elastic.Documentation.Search/NavigationSearchGateway.cssrc/services/Elastic.Documentation.Search/ServicesExtension.cssrc/tooling/docs-builder/Http/DocumentationWebHost.cssrc/tooling/docs-builder/Http/StaticWebHost.cssrc/tooling/docs-builder/docs-builder.csprojtests-integration/Elastic.Assembler.IntegrationTests/Elastic.Assembler.IntegrationTests.csprojtests-integration/Elastic.Assembler.IntegrationTests/Search/SearchIntegrationTests.cstests-integration/Elastic.Documentation.Api.IntegrationTests/AskAiGatewayStreamingTests.cstests-integration/Elastic.Documentation.Api.IntegrationTests/Elastic.Documentation.Api.IntegrationTests.csprojtests-integration/Elastic.Documentation.Api.IntegrationTests/EuidEnrichmentIntegrationTests.cstests-integration/Elastic.Documentation.Api.IntegrationTests/Fixtures/ApiWebApplicationFactory.cstests-integration/Elastic.Documentation.Api.IntegrationTests/OtlpProxyIntegrationTests.cstests-integration/Mcp.Remote.IntegrationTests/Mcp.Remote.IntegrationTests.csprojtests-integration/Mcp.Remote.IntegrationTests/McpToolsIntegrationTestsBase.cstests-integration/Search.IntegrationTests/Search.IntegrationTests.csprojtests/Elastic.Documentation.Api.Infrastructure.Tests/Adapters/AskAi/StreamTransformerTests.cstests/Elastic.Documentation.Api.Infrastructure.Tests/Caching/DistributedCacheTests.cstests/Elastic.Documentation.Api.Infrastructure.Tests/Elastic.Documentation.Api.Infrastructure.Tests.csproj
💤 Files with no reviewable changes (11)
- src/api/Elastic.Documentation.Api.Core/Elastic.Documentation.Api.Core.csproj
- src/api/Elastic.Documentation.Api.Infrastructure/Elastic.Documentation.Api.Infrastructure.csproj
- src/services/Elastic.Documentation.Search/FullSearchGateway.cs
- src/services/Elastic.Documentation.Search/MockSearchGateway.cs
- src/services/Elastic.Documentation.Search/NavigationSearchGateway.cs
- src/services/Elastic.Documentation.Search/ChangesGateway.cs
- tests-integration/Mcp.Remote.IntegrationTests/McpToolsIntegrationTestsBase.cs
- tests-integration/Mcp.Remote.IntegrationTests/Mcp.Remote.IntegrationTests.csproj
- src/api/Elastic.Documentation.Mcp.Remote/Elastic.Documentation.Mcp.Remote.csproj
- src/services/Elastic.Documentation.Search/Elastic.Documentation.Search.csproj
- src/api/Elastic.Documentation.Api.App/Elastic.Documentation.Api.App.csproj
…x-x64 Enables arm64 image builds to publish and copy the correct architecture artifact rather than always using the amd64 binary. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…r key - Register McpTools ActivitySource in Mcp.Remote TracerProvider so MCP spans are captured - Use GetEntryAssembly() over GetCallingAssembly() for service.version so the host service's version is reported rather than ServiceDefaults - OtlpProxyOptions now checks OTEL_EXPORTER_OTLP_ENDPOINT first then OTLP_PROXY_ENDPOINT as backward-compatible fallback, matching the documented priority order Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Wanted to share a different read on the motivation before we collapse the projects.
The split was never about external reuse. None of us expected another repo to consume Api.Core. It was there to enforce the
Clean Architecture dependency rule (Ports & Adapters): source dependencies point in one direction only, App -> Infrastructure
-> Core, and the use cases never depend on low level details like Elasticsearch, AWS, HTTP or OTel exporters.
A few reasons that matters:
-
Dependency Inversion at the boundary. Core defines ports (
IAskAiGateway,IFullSearchGateway,IDistributedCache) and the
use cases depend on those interfaces. Infrastructure provides the adapters. Core owns the contract, Infrastructure conforms to it.
The moment Core can see Infrastructure types, the arrow flips and the use case starts depending on the vendor. -
Stable depends on stable. The use cases (ask AI, search navigation, record feedback) are the most stable thing we have. The
Elasticsearch client, DynamoDB SDK, GCP token provider and OTel exporter are the least stable, they version, get swapped, get
reconfigured. The current layering means churn in the volatile layer can't ripple back into the stable one. Merge them and any
infra change is free to touch a use case. -
Framework independence. Core doesn't reference
Microsoft.AspNetCore.App, the ES client, AWS SDK or any OTel package, only
logging and config abstractions. That's what lets us reason about and unit test the use cases without a web host or a live ES.
Once everything isMicrosoft.NET.Sdk.Webin one assembly, that property is gone. -
The compiler is the enforcement mechanism. Project boundaries are checked by the build, folders are checked by reviewers on a
good day. "Core must not reference Infrastructure" is currently impossible to violate. After the merge it becomes a convention
that decays the first time someone is in a hurry, and the symptom (a use casenew-ing anElasticsearchClient) won't show up
until it has already shipped.
On Mcp.Remote, agreed the current shape is awkward, but I'd read it as evidence for the split rather than against it. The only
thing pulling Infrastructure into Mcp.Remote is the OTel helper. The search interfaces it uses are already satisfied by the tiny
Core assembly, because the ports live in Core. Your PR already does the right thing for that piece by lifting AddEuidEnrichment
into ServiceDefaults and moving the gateway interfaces into services/Elastic.Documentation.Search. Both moves are good on
their own, and after them Mcp.Remote drops its Infrastructure reference without needing the Api projects to merge. We could ship
those two extractions and leave Core/Infrastructure/App intact: Mcp.Remote ends up exactly as clean as the PR description targets,
the Api keeps its compile time guardrail, and the dependency rule still holds.
Happy to split the OTel/search extractions out as their own PR if that's useful, those wins don't require deleting the boundary.
|
Appreciate the deep read — and I think we're closer than it looks, just optimizing on different axes. The shape I'm working toward is vertical slice architecture, not the merge-and-flatten you might be reading PR1 as. Concretely:
On testability specifically:
I don't think this depends on the project split. Tests depend on services — most of which do contain implementations, that's the practical reality — but the unit-test seam is the interface. We can mock The thing your model gives up that mine doesn't is the compiler firewall: "use case cannot accidentally I think both our shapes honor the load-bearing tenets of Clean Architecture — dependency inversion at the abstraction, framework-independent contracts, testable units, technology-free domain. We just optimize for different things. Your model optimizes for intra-app vendor isolation with the compiler as the enforcement mechanism. Mine optimizes for cross-repo sharing with the abstraction (interface or domain record) as the enforcement mechanism, and The concrete goal driving this is sharing our domain and service interfaces with |
|
One quick factual clarification on the Mcp.Remote framing:
PR1 step 2 just relocated the interface to live next to its impl ( |
|
One more on this specifically:
This isn't quite accurate. Mcp.Remote never depended on the So the pre-PR1 reality: Mcp.Remote referenced both Api.Core (for the interface) and services/Search (for the impl that satisfies it). One vertical concern, two projects. PR1 step 2 just moved the interface to live next to its impl — the same place Mcp.Remote was already reaching for code. And critically: Mcp.Remote's consumption stays identical across PR1 and PR2. It depended on a service before, it depends on a service now ( Reading Mcp.Remote as evidence for the split assumes the Core port was load-bearing for that consumer. In practice it was an extra hop with no benefit — the impl wasn't swappable, the interface wasn't satisfied by anything other than what services/Search provides, and Mcp.Remote was never going to construct its own impl. |
|
@coderabbitai review |
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
✅ Actions performedReview triggered.
|
|
@reakaleek and I discussed offline. Ultimately it boils down to Simple Clean Architecture vs Vertical Slice (Clean) Architecture. Both solve the tenants of clean architecture, the change now to VSA was mostly precipicated because we already follow it for most or our |
* Merge Elastic.Documentation.Api.* into single project Three projects (Api.Core, Api.Infrastructure, Api.App) collapsed into Elastic.Documentation.Api. Mcp.Remote no longer references any Api.* project — its only deps are ServiceDefaults, services/Search, and services/Assembler. Key moves: - EuidSpanProcessor + EuidLogProcessor → ServiceDefaults (shared) - AddEuidEnrichment helper added to ServiceDefaults - Search gateway interfaces (IFullSearchGateway, INavigationSearchGateway, IChangesGateway) → services/Elastic.Documentation.Search - McpToolSourceName constant inlined into Mcp.Remote Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Fix C# import ordering and name simplification after Api merge Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Fix Dockerfile publish RID to use TARGETARCH/TARGETOS instead of linux-x64 Enables arm64 image builds to publish and copy the correct architecture artifact rather than always using the amd64 binary. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Address PR review: OTel source registration, assembly version, env var key - Register McpTools ActivitySource in Mcp.Remote TracerProvider so MCP spans are captured - Use GetEntryAssembly() over GetCallingAssembly() for service.version so the host service's version is reported rather than ServiceDefaults - OtlpProxyOptions now checks OTEL_EXPORTER_OTLP_ENDPOINT first then OTLP_PROXY_ENDPOINT as backward-compatible fallback, matching the documented priority order Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Fix import ordering in Mcp.Remote Program.cs Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * [2/5] VSA: Collapse Gateway+Usecase into *Service per feature (#3335) Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Why
The three-project split (Api.Core / Api.Infrastructure / Api.App) implemented a hexagonal layering that provided no external reuse benefit — all consumers are internal to this repo. The same split caused Mcp.Remote to accumulate a dependency on the full Api stack just to call one OTel helper and use two search interfaces, making it harder to reason about and maintain independently.
What
Elastic.Documentation.Api.Core,Api.Infrastructure, andApi.Appare collapsed into a singleElastic.Documentation.Apiproject (SDK.Web, AOT-publishable, sameAssemblyName=docs-builder-api).EuidSpanProcessor,EuidLogProcessor, and a newAddEuidEnrichmenthelper move toElastic.Documentation.ServiceDefaultsso any web app in the repo can enrich spans/logs with the user euid without pulling in the full Api project.IFullSearchGateway,INavigationSearchGateway,IChangesGateway) and their DTOs move toservices/Elastic.Documentation.Searchalongside their implementations — the Search service no longer references the Api project at all.ServiceDefaults+services/Search+services/Assembleronly.How
AddDocsApiOpenTelemetryin the merged project becomes a thin wrapper: it calls the newAddEuidEnrichment(which owns the Elastic OTel SDK init, service.version wiring, euid cookie enrichment, and processors) and then adds the five API-specific activity sources on top. Mcp.Remote callsAddEuidEnrichmentdirectly instead.Part of a move to vertical slice architecture to be able to cleanly lift search contract: