Skip to content

A geo module is called with the request, as a device module is - #1374

Draft
jwrosewell wants to merge 14 commits into
feature/32-cache-featuresfrom
feature/33-geo-call
Draft

jwrosewell wants to merge 14 commits into
feature/32-cache-featuresfrom
feature/33-geo-call

Conversation

@jwrosewell

Copy link
Copy Markdown
Collaborator

Summary

Changes

Area Change
crates/trusted-server-core/src 12 files, 574 lines added, 73 removed
crates/trusted-server-adapter-fastly/src 4 files, 167 lines added, 31 removed
docs/guide 2 files, 26 lines added, 18 removed
crates/cmp/didomi 1 file, 22 lines added, 2 removed
crates/testing/seam-probe 1 file, 21 lines added, 5 removed
crates/trusted-server-adapter-axum/tests 1 file, 6 lines added, 3 removed
crates/trusted-server-cli/src 1 file, 6 lines added, 3 removed
crates/auction/prebid-server 1 file, 3 lines added, 1 removed
crates/loading-cache/51degrees 1 file, 3 lines added, 1 removed
CHANGELOG.md 1 file, 1 line added, 0 removed
crates/trusted-server-adapter-spin/src 1 file, 1 line added, 9 removed

Closes

Closes #1321

Test plan

CI: 19 of 19 checks green, the fork's CI on 1b8b438 (jwrosewell#28).

  • cargo test-fastly && cargo test-axum
  • cargo clippy-fastly && cargo clippy-axum
  • cargo fmt --all -- --check
  • JS tests: cd crates/trusted-server-js/lib && npx vitest run
  • JS format: cd crates/trusted-server-js/lib && npm run format
  • Docs format: cd docs && npm run format
  • WASM build: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1
  • Manual testing via fastly compute serve
  • Other: the full workflow set of this repository, run on the pull request named above

Checklist

  • Changes follow AGENTS.md conventions
  • No unwrap() in production code (clippy denies unwrap_used in CI)
  • Uses log macros as AGENTS.md asks (not println!)
  • New code has tests
  • No secrets or credentials committed

On top of a module declares the caches it loads through (#27). Issue: #1321.

What this adds

A geo module is a GeoModule, called with the request's module call as a device module is, locate(call), so it sees the request's evidence, the settings and the services, and a module whose one service answers location and device from the same request can answer both from one call. A module that resolves from the client address alone keeps implementing PlatformGeo and is offered through PlatformGeoModule::new(name, module), which hands the platform lookup the address the call carries and asks nothing when the call carries no services.

RuntimeServices::geo and IntegrationRegistration::with_geo_module take an Arc<dyn GeoModule>, build_geo_module returns one (platform wraps the host's lookup, anything else the disabled one), and the registry resolves [geo] module to a GeoModule. Core builds the call in one place, locate_request on dyn GeoModule, from the request's view, its headers, the settings and the services. The Edge Cookie path, the auction endpoint, the Didomi module and the seam probe call it with the request's headers. The Fastly adapter's two finalize paths call it without headers, since none survive to them, and the module on those paths is the host's own lookup or the disabled one, chosen by build_geo_module, which reads the address alone.

#[async_trait::async_trait(?Send)]
impl GeoModule for OneService {
    fn id(&self) -> &'static str { "geo.example" }
    async fn locate(&self, call: ModuleCall<'_>) -> Result<Option<GeoInfo>, Report<PlatformError>> {
        let evidence = <&dyn RequestInfo>::from_module_context(&call)?;
        self.answer_for(evidence).await
    }
}

Why

A geo module was called as lookup(client_ip, services), with the address and nothing else of the request, while a device module is called with the request's evidence. A vendor whose one service answers location and device from the same request could not make the geo answer from the same call as the device answer, so either the two were asked separately or the geo answer depended on the device module having run first on the same adapter. The adapters resolved geo in four places with the request in hand and handed the module only the address.

What breaks

Crates outside core that passed a PlatformGeo to with_geo_module or RuntimeServices::geo wrap it in PlatformGeoModule::new(name, module), or implement GeoModule to see the request. The Didomi module's geo call is rewritten to go through locate_request, and reads the resolved GeoInfo as before. Nothing in a deployment's documents changes.

The commits

Commit What it does
d66b7ce A geo module is called with the request, as a device module is
dbaa36b The ts CLI's local services call the disabled geo as a module
9800980 Review: core builds the geo call in one helper, and the Fastly finalize paths say they call the host's module without headers
5a1938d The review fix below merged in

CI passed on dbaa36b (d66b7ce passed every job but the CLI ones), and runs on 5a1938d.

What is not in this pull request

  • A geo module in the stock list that reads more than the address. The host platforms' geo modules resolve from the address and are wrapped.

jwrosewell and others added 14 commits October 13, 2026 03:48
locate_request on dyn GeoModule builds the call every geo call carries, the request as evidence from its headers and the client address, the settings when the caller has them, the services and the client, and the Edge Cookie path, the auction endpoint, the Didomi module and the seam probe call it. The Fastly adapter's two finalize paths call it without headers and say why: the module there is the host's own lookup or the disabled one, chosen by build_geo_module, which reads the address alone, so the middleware no longer clones the headers for it.
…the deployment selects

The finalize step that writes the x-geo-* headers on every response built
its geo module with build_geo_module alone, which answers the host's own
lookup for "platform" and the disabled module for every other name,
because it cannot see the registry. A deployment whose [geo] module names
a module from the registry therefore got no location on that path, and
every page carried x-geo-info-available: false, while the same module
answered the request path.

The adapter now hands the finalize step the registry's module when the
selector names one, through finalize_geo_module, and the host's own lookup
or the disabled module otherwise, as before. The step keeps the request's
headers before the handler takes the request and hands them to the module
as its evidence, so a module that reads the request, as a device module
does, is called the way the request path calls it, and the answer it holds
for the same evidence from earlier in the request is the one it gives. The
note on locate_request no longer says the finalize paths never call a
registry module.

A test proves the finalize step hands a geo module the request's headers,
with a module that records the user agent of the evidence it is called
with.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants