Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
d22f0d9
Each adapter names its own backends
jwrosewell Oct 14, 2026
9b837d5
The planned instances test expects the exact bounded budget
jwrosewell Oct 14, 2026
4d0b74b
Merge 44-client-hints into 47-backend-naming
jwrosewell Oct 14, 2026
500ac26
The 51Degrees shared crate's test backend names no naming policy
jwrosewell Oct 14, 2026
7797518
Merge 46-last-names into 47-backend-naming
jwrosewell Oct 14, 2026
e87cd70
Merge 46-last-names into 47-backend-naming
jwrosewell Oct 15, 2026
8077b4f
Merge 46-last-names into 47-backend-naming
jwrosewell Oct 15, 2026
4ce9618
Merge 46-last-names into 47-backend-naming
jwrosewell Oct 15, 2026
d773ed9
Merge 46-last-names into 47-backend-naming
jwrosewell Oct 15, 2026
2dbc2d7
Merge 46-last-names into 47-backend-naming
jwrosewell Oct 15, 2026
9699053
The backend naming design names the callers the policy had and what b…
jwrosewell Oct 15, 2026
c0c9548
The backend naming design takes its round 2 corrections
jwrosewell Oct 15, 2026
32556b9
Merge 46-last-names into 47-backend-naming
jwrosewell Oct 15, 2026
d2e67e4
Merge 46-last-names into 47-backend-naming
jwrosewell Oct 15, 2026
c9a6acd
Merge 46-last-names into 47-backend-naming
jwrosewell Oct 15, 2026
9c144ef
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
43a64b2
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
9e3f03c
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
c1c6169
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
607d4a2
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
eab8e4f
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
4609cf4
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
1d691aa
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
ef714b7
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
93ae784
The device module's capped backend follows the backend trait without …
jwrosewell Oct 16, 2026
1d637b4
The design header names the pull request that carries it
jwrosewell Oct 16, 2026
334c47e
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
0fcb185
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
a747c4a
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
9928b76
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
f94a5f3
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
1b604e6
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
d865d1b
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
9eac061
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 16, 2026
c3de597
The Axum adapter says it does not honor certificate_check = false
jwrosewell Oct 17, 2026
c5d6bc6
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 18, 2026
5220b9c
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 18, 2026
70edeaa
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 18, 2026
6ea0643
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 18, 2026
d10507b
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 18, 2026
7828c0c
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 18, 2026
1528866
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 19, 2026
683d98a
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 19, 2026
b3fc581
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 19, 2026
4b17b10
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 19, 2026
3af8768
Merge split/46-last-names into split/47-backend-naming
jwrosewell Oct 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- **Breaking for crates outside core:** an adapter answers how it names a backend and bounds its transport timers through `PlatformBackend` alone, which gains `auction_backend_budget` and `transport_timeout_bucket_count` with defaults, and describes itself to auction plan validation through an `AuctionTargetDescriptor` it builds from its name, its backend implementation and its `AuctionTargetCapabilities`. `AuctionPlan::validate_for_target` takes that descriptor, and `BackendNamingPolicy`, `AuctionTargetId`, `PredictedBackend`, `BackendNamingError` and `PlatformBackend::naming_policy` are gone from core, because core names no hosting platform. Each adapter carries its own naming policy beside the code that registers its backends, and the names a deployment sees are unchanged.
- Core names no platform's cache header. It removes from a response it makes private only `Surrogate-Control`, `CDN-Cache-Control` and a platform header it wrote its own policy in, so on every adapter `fastly-surrogate-control` and `cloudflare-cdn-cache-control` stay where an origin or an operator wrote them, until a fetch middleware removes them, which none in the stack does yet. The Fastly and Cloudflare adapters declare their own platform's header, which keeps an origin response with a policy in it out of the stored copies on that adapter alone, and each adapter writes its policy in the header it wrote before. **For crates outside core**, `EdgeCacheHeader::CloudflareCdnCacheControl` is `EdgeCacheHeader::Platform(name)`, `EDGE_CACHE_HEADER_NAMES` lists only the two standard headers, the two platform header constants leave core, an adapter declares its platform's headers with `platform::declare_platform_cache_headers`, and a policy written with `CachePolicy::apply_to_response` is removed by `cache_policy::remove_owned_cache_headers` when the response is made private.
- Auction telemetry is an analytics module from a crate of its own. `[analytics]` is read as every module type's section is, and the Tinybird module, its settings and its checks live in `crates/analytics/tinybird`, which each adapter and the `ts` tool list among the modules they link, where the Fastly adapter alone built it. The Fastly adapter alone hands the module's sink to the auction, so the Axum, Cloudflare and Spin adapters link the module and still send nothing. The settings a deployment writes do not change. A module named in full in its own type's section, as `analytics.tinybird` in `[analytics]`, reads its table without the type folder, at `[analytics.tinybird]`, when the settings load, at deploy and when a stored configuration loads. A table missing its host or token, a key the table does not know and a reserved setting turned on are refused when the deployment starts and by `ts config validate`, naming the table, where they were refused as the settings loaded. A `[tinybird]` table at the top level is refused as the section of a module type nothing supplies. **For crates outside core**, a registration declares the sink it supplies with `IntegrationRegistrationBuilder::with_analytics_module`, and `IntegrationRegistry::analytics_module` gives the one `[analytics] module` selected. `Settings::analytics_module`, `AnalyticsConfig` and `TinybirdSettings` leave core.
- **Breaking for crates outside core:** core's auction names no protocol. A demand source implements `CompiledDemand`, which builds its own request from the routed input (`build_request`, answering the body, the headers, the slot ids and any state kept for the answer) and reads its own answer, and core sends what it built. OpenRTB 2.6 is the protocol module `crates/auction-protocol/openrtb`, where the request builder, the response reader, the wire types with their extension objects, and `OpenRtbDemand`, the hook trait a demand implementation that speaks OpenRTB fills in, now live, run through core's seam by `OpenRtbDriver`. `auction::openrtb` and core's `openrtb` module are gone, and core's own manifest keeps the generated types only for the response `POST /auction` returns. The dimension admission helpers are `auction::admission`, `apply_notification_policy` and `ignored_bidder_params_count` are `auction::provider`, and `Eid` and `Uid` are `ec::eids`. A deployment's settings do not change.
Expand Down Expand Up @@ -115,6 +116,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The origin's address in a React Server Components Flight response is now moved in one pass. A request host that begins with the origin host was matched again inside its own replacement, so `https://publisher.example/` served on `publisher.example.localhost` over `http` came out as `http://publisher.example.localhost.localhost.localhost/`, and on `publisher.example:8443` over `https` as `https://publisher.example:8443:8443/`.
- Server-side ad template bids now always carry `hb_adid` in `window.tsjs.bids`. Bidders that return neither a Prebid Cache UUID nor an `adid` previously produced no `hb_adid` at all, so no `hb_adid` GPT targeting key was set and the Universal Creative render bridge had nothing to match — the winning creative never rendered. The OpenRTB bid `id`, which is mandatory per spec, is now the last-resort source; `cache_id` and `adid` still take priority where present. Blank `cacheId`/`adid` values no longer win that precedence and emit an unusable empty `hb_adid`, and `hb_cache_host`/`hb_cache_path` are now emitted only alongside a real Prebid Cache UUID — without one they pointed the Universal Creative at a guaranteed cache miss instead of letting it fall through to the inline creative.
- The origin's address in a CSS, JavaScript, JSON or other text response that core rewrites without parsing it is now replaced in one pass. A request host that contains the origin host was matched again inside its own replacement, so `https://example.com/` served on `www.example.com` came out as `https://www.www.example.com/`, and `https://publisher.example/` served on `publisher.example.localhost` over `http` as `http://publisher.example.localhost.localhost.localhost.localhost/`.
- The Axum adapter warns once at startup when `proxy.certificate_check` is `false`, which it does not honor because its HTTP client always verifies HTTPS origin certificates. The configuration and Axum guides say that only the Fastly adapter honors `false`, and that the Cloudflare and Spin adapters use the setting only in a backend's name ([#1132](https://github.com/IABTechLab/trusted-server/issues/1132)).
- A request that names its host only in its URI's authority, as an HTTP/2 request with no `Host` header may, is now processed. The request host was read from `Forwarded`, `X-Forwarded-Host` and `Host` alone, so such a request had an empty host and its page was returned unmodified, with no URL rewriting, middleware, injected script or auction. The authority is read last, so each of those headers still takes precedence ([#1118](https://github.com/IABTechLab/trusted-server/issues/1118)).

### Added
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 3 additions & 7 deletions crates/analytics/tinybird/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,9 @@ mod tests {
use trusted_server_core::config_payload::settings_from_config_blob_with;
use trusted_server_core::integrations::IntegrationRegistry;
use trusted_server_core::platform::{
BackendNamingPolicy, ClientInfo, PlatformBackend, PlatformConfigStore, PlatformError,
PlatformGeo, PlatformHttpClient, PlatformPendingRequest, PlatformResponse,
PlatformSecretStore, PlatformSelectResult, RuntimeServices, StoreId, StoreName,
ClientInfo, PlatformBackend, PlatformConfigStore, PlatformError, PlatformGeo,
PlatformHttpClient, PlatformPendingRequest, PlatformResponse, PlatformSecretStore,
PlatformSelectResult, RuntimeServices, StoreId, StoreName,
};
use trusted_server_core::test_support::template::{
template_with_resolved_required_secrets, uncomment_block,
Expand Down Expand Up @@ -539,10 +539,6 @@ mod tests {
}

impl PlatformBackend for RecordingBackend {
fn naming_policy(&self) -> BackendNamingPolicy {
BackendNamingPolicy::Fastly
}

fn predict_name(
&self,
_spec: &PlatformBackendSpec,
Expand Down
10 changes: 5 additions & 5 deletions crates/auction/aps/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3627,10 +3627,10 @@ mod engine_tests {
AdFormat, AdSlot, AuctionContext, AuctionRequest, BidStatus, MediaType, PublisherInfo,
UserInfo,
};
use trusted_server_core::platform::PlatformResponse;
use trusted_server_core::platform::test_support::{
NamingBackend, StubHttpClient, build_services_with_backend_and_http_client,
};
use trusted_server_core::platform::{BackendNamingPolicy, PlatformResponse};
use trusted_server_core::test_support::tests::create_test_settings;

use super::{APS_RENDERER_TYPE, ApsRendererV1, MODULE, builder};
Expand Down Expand Up @@ -4131,7 +4131,7 @@ mod engine_tests {
async fn planned_aps_transport_omits_accept_header() {
let http = Arc::new(StubHttpClient::new());
http.push_response(400, Vec::new());
let backend = Arc::new(NamingBackend::new(BackendNamingPolicy::Fastly));
let backend = Arc::new(NamingBackend::new());
let services = build_services_with_backend_and_http_client(
Arc::clone(&backend) as Arc<_>,
Arc::clone(&http) as Arc<_>,
Expand Down Expand Up @@ -4212,7 +4212,7 @@ mod engine_tests {
("authorization", "fictional-secret"),
],
);
let backend = Arc::new(NamingBackend::new(BackendNamingPolicy::Fastly));
let backend = Arc::new(NamingBackend::new());
let services = build_services_with_backend_and_http_client(
Arc::clone(&backend) as Arc<_>,
Arc::clone(&http) as Arc<_>,
Expand Down Expand Up @@ -4336,7 +4336,7 @@ mod engine_tests {
.expect("should serialize APS instance response"),
);
}
let backend = Arc::new(NamingBackend::new(BackendNamingPolicy::Fastly));
let backend = Arc::new(NamingBackend::new());
let services = build_services_with_backend_and_http_client(
Arc::clone(&backend) as Arc<_>,
Arc::clone(&http) as Arc<_>,
Expand Down Expand Up @@ -4573,7 +4573,7 @@ mod engine_tests {
let (profile, provider_id) = ("aps", "aps_instance");
let http = Arc::new(StubHttpClient::new());
http.push_response(200, vec![b'x'; 1024 * 1024 + 1]);
let backend = Arc::new(NamingBackend::new(BackendNamingPolicy::Axum));
let backend = Arc::new(NamingBackend::new());
let services = build_services_with_backend_and_http_client(
Arc::clone(&backend) as Arc<_>,
Arc::clone(&http) as Arc<_>,
Expand Down
7 changes: 1 addition & 6 deletions crates/auction/prebid-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2898,10 +2898,6 @@ mod tests {
struct PredictOnlyBackend;

impl PlatformBackend for PredictOnlyBackend {
fn naming_policy(&self) -> trusted_server_core::platform::BackendNamingPolicy {
trusted_server_core::platform::BackendNamingPolicy::Axum
}

fn predict_name(
&self,
spec: &PlatformBackendSpec,
Expand Down Expand Up @@ -7448,7 +7444,6 @@ mod engine_tests {
};
use trusted_server_core::consent::jurisdiction::Jurisdiction;
use trusted_server_core::consent::{ConsentContext, ConsentSource, SignalRecord, SignalStatus};
use trusted_server_core::platform::BackendNamingPolicy;
use trusted_server_core::platform::test_support::{
NamingBackend, StubHttpClient, build_services_with_backend_and_http_client,
};
Expand Down Expand Up @@ -8088,7 +8083,7 @@ mod engine_tests {
}))
.expect("should serialize PBS response b"),
);
let backend = Arc::new(NamingBackend::new(BackendNamingPolicy::Fastly));
let backend = Arc::new(NamingBackend::new());
let services = build_services_with_backend_and_http_client(
Arc::clone(&backend) as Arc<_>,
Arc::clone(&http) as Arc<_>,
Expand Down
8 changes: 2 additions & 6 deletions crates/shared/51degrees/src/test_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use edgezero_core::body::Body as EdgeBody;
use edgezero_core::key_value_store::{KvError, KvPage, KvStore};
use error_stack::Report;
use trusted_server_core::platform::{
BackendNamingPolicy, ClientInfo, DisabledGeo, PlatformBackend, PlatformBackendSpec,
PlatformConfigStore, PlatformError, PlatformGeoModule, PlatformHttpClient, PlatformHttpRequest,
ClientInfo, DisabledGeo, PlatformBackend, PlatformBackendSpec, PlatformConfigStore,
PlatformError, PlatformGeoModule, PlatformHttpClient, PlatformHttpRequest,
PlatformPendingRequest, PlatformResponse, PlatformSecretStore, PlatformSelectResult,
PlatformValueCache, RuntimeServices, StoreId, StoreName, UnavailableKvStore,
};
Expand Down Expand Up @@ -71,10 +71,6 @@ impl PlatformSecretStore for StubStore {
pub struct ResolvingBackend;

impl PlatformBackend for ResolvingBackend {
fn naming_policy(&self) -> BackendNamingPolicy {
BackendNamingPolicy::Axum
}

fn predict_name(&self, _spec: &PlatformBackendSpec) -> Result<String, Report<PlatformError>> {
Ok("fiftyone_degrees_test".to_owned())
}
Expand Down
9 changes: 2 additions & 7 deletions crates/testing/iabtl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1040,9 +1040,8 @@ mod tests {

use sha2::{Digest as _, Sha256};
use trusted_server_core::platform::{
BackendNamingPolicy, ClientInfo, DisabledGeo, PlatformBackend, PlatformBackendSpec,
PlatformConfigStore, PlatformSecretStore, StoreId, StoreName, UnavailableHttpClient,
UnavailableKvStore,
ClientInfo, DisabledGeo, PlatformBackend, PlatformBackendSpec, PlatformConfigStore,
PlatformSecretStore, StoreId, StoreName, UnavailableHttpClient, UnavailableKvStore,
};

/// Config store that answers nothing, so a test can build
Expand Down Expand Up @@ -1105,10 +1104,6 @@ mod tests {
struct StubBackend;

impl PlatformBackend for StubBackend {
fn naming_policy(&self) -> BackendNamingPolicy {
BackendNamingPolicy::Axum
}

fn predict_name(
&self,
_spec: &PlatformBackendSpec,
Expand Down
106 changes: 105 additions & 1 deletion crates/trusted-server-adapter-axum/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ fn build_state_with_registrations_and_services(
// here. Compiling without them would drop the implementation and report the
// name as one no builder registers.
let plan = Arc::new(compile_auction_plan_with(&settings, integrations)?);
plan.validate_for_target(trusted_server_core::platform::AuctionTargetId::Axum)?;
plan.validate_for_target(&crate::platform::auction_target())?;
let orchestrator = build_orchestrator_with_plan(Arc::clone(&plan))?;
let registry = IntegrationRegistry::with_plan_and_registrations(&settings, plan, integrations)?
.with_platform(PlatformIdentity::named("axum"));
Expand Down Expand Up @@ -288,6 +288,7 @@ fn build_state_with_registrations_and_services(
"Axum",
&UNSENT_ANALYTICS_LOGGED,
);
warn_when_certificate_check_is_off(&settings);

Ok(Arc::new(AppState {
settings: Arc::new(settings),
Expand All @@ -299,6 +300,21 @@ fn build_state_with_registrations_and_services(
}))
}

/// Warns that `proxy.certificate_check = false` is not honored here.
///
/// This adapter's HTTP client always verifies an HTTPS origin's certificate,
/// so a deployment that turned the check off for a self-signed origin would
/// otherwise see only failed requests. The state is built once for the
/// process, so this warns once, at startup.
fn warn_when_certificate_check_is_off(settings: &Settings) {
if !settings.proxy.certificate_check {
log::warn!(
"proxy.certificate_check is false, but the Axum adapter does not honor it \
and still verifies every HTTPS origin's certificate"
);
}
}

/// Refuses `[geo] module = "platform"`, because this host has no geo lookup.
///
/// The selection would locate no request and still count as a geo module,
Expand Down Expand Up @@ -1065,6 +1081,94 @@ mod tests {
assert_eq!(services.public_prefix().as_str(), "ts");
}

/// What `run` returns, and each line it logs on this thread at warning
/// level or above, as the level and then the message.
fn warnings_logged_by<T>(run: impl FnOnce() -> T) -> (T, Vec<String>) {
use std::cell::RefCell;
use std::sync::Once;

thread_local! {
static CAPTURED: RefCell<Option<Vec<String>>> = const { RefCell::new(None) };
}

struct ThreadLogger;

impl log::Log for ThreadLogger {
fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool {
CAPTURED.with(|captured| captured.borrow().is_some())
}

fn log(&self, record: &log::Record<'_>) {
CAPTURED.with(|captured| {
if let Some(lines) = captured.borrow_mut().as_mut() {
lines.push(format!("{} {}", record.level(), record.args()));
}
});
}

fn flush(&self) {}
}

static LOGGER: ThreadLogger = ThreadLogger;
static INSTALL: Once = Once::new();

INSTALL.call_once(|| {
log::set_logger(&LOGGER).expect("should be the only logger these tests install");
log::set_max_level(log::LevelFilter::Warn);
});
CAPTURED.with(|captured| *captured.borrow_mut() = Some(Vec::new()));
let output = run();
let lines = CAPTURED.with(|captured| captured.borrow_mut().take().unwrap_or_default());
(output, lines)
}

/// This adapter's HTTP client always verifies an origin's certificate, so
/// startup with `proxy.certificate_check = false` warns once that the
/// setting is not honored, and startup with the check on warns nothing.
#[test]
fn startup_warns_once_that_turning_the_certificate_check_off_is_not_honored() {
let warnings_naming_the_check = |certificate_check: bool| {
let settings = Settings::from_toml(&format!(
r#"
[source]
module = "origin"

[source.origin]
url = "https://origin.publisher.example"

[publisher]
domain = "www.publisher.example"
cookie_domain = ".publisher.example"
proxy_secret = "fictional-proxy-secret-32-bytes-ok"

[proxy]
certificate_check = {certificate_check}

[geo]
assume_single_jurisdiction = true
"#
))
.expect("should parse the settings");
let (state, lines) = warnings_logged_by(|| build_state_with_settings(settings));
state.expect("should build the state");
lines
.into_iter()
.filter(|line| line.contains("proxy.certificate_check"))
.collect::<Vec<_>>()
};

let off = warnings_naming_the_check(false);
assert_eq!(off.len(), 1, "should warn once: {off:?}");
assert!(
off[0].starts_with("WARN ") && off[0].contains("still verifies"),
"should say certificates are still verified: {off:?}"
);
assert!(
warnings_naming_the_check(true).is_empty(),
"should not warn while the check is on"
);
}

/// Builds application state directly, bypassing the composition root's
/// startup check, so the per-request behavior can be exercised with a
/// selection the adapter cannot supply.
Expand Down
Loading
Loading