Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bfde031
Address fourth-round review feedback on backend naming and quantization
prk-Jr Jul 17, 2026
33384cd
Tolerate trailing garbage after the final gzip member
prk-Jr Jul 17, 2026
911268f
Borrow post-release chunks in the auction hold step
prk-Jr Jul 17, 2026
666b3cb
Document the post-processor buffering exception as out of scope
prk-Jr Jul 17, 2026
966c856
Add /_ts/trace overlay to confirm creatives render on the page
prk-Jr Jul 18, 2026
e1a8b81
Trace client-side /auction renders and stop overclaiming GAM renders
prk-Jr Jul 18, 2026
94caef1
Merge branch 'main' into quantize-auction-transport-timeouts
aram356 Jul 19, 2026
eacd6b0
Merge branch 'main' into streaming-pipeline-async-core
aram356 Jul 19, 2026
4f45974
Recover GPT slots orphaned by a client re-render
prk-Jr Jul 18, 2026
9e46233
Show the render trace as a timeline and badge every rendered slot
prk-Jr Jul 18, 2026
27a4727
Stop attributing GAM refreshes to the finished server-side auction
prk-Jr Jul 20, 2026
7b7c466
Number every render and keep GAM's fill signal on refresh rows
prk-Jr Jul 20, 2026
2c482d7
Show absent attribution as — instead of ? in trace tooltips
prk-Jr Jul 20, 2026
50ce368
Trace SSAT creatives the Universal Creative bridge renders inline
prk-Jr Jul 20, 2026
b0f31ef
Fall back to the OpenRTB bid id for hb_adid when cache_id and ad_id a…
prk-Jr Jul 20, 2026
ba75a83
Merge branch 'main' into quantize-auction-transport-timeouts
aram356 Jul 21, 2026
23f8e5b
Merge branch 'main' into streaming-pipeline-async-core
aram356 Jul 21, 2026
09c8c4a
Merge branch 'main' into quantize-auction-transport-timeouts
aram356 Jul 22, 2026
e46085a
Merge branch 'main' into streaming-pipeline-async-core
aram356 Jul 22, 2026
7af1b5f
Correlate dispatched auction bids by resolved backend name
prk-Jr Jul 22, 2026
69a2e70
Bound streaming decode allocation and fix buffered gzip decoding
prk-Jr Jul 22, 2026
d434943
Merge branch 'main' into feat/ssat-render-inline-creative
prk-Jr Jul 22, 2026
2ad899f
Expand ${AUCTION_PRICE} in win and billing notification URLs
prk-Jr Jul 22, 2026
07890a9
Suppress fabricated empty Prebid bidder params (#910)
prk-Jr Jul 22, 2026
3a6b7fd
Merge remote-tracking branch 'origin/main' into trace-auction-winners…
prk-Jr Jul 22, 2026
03aa5ec
Correct render tracing across auction paths
prk-Jr Jul 22, 2026
99ad303
Merge remote-tracking branch 'origin/quantize-auction-transport-timeo…
prk-Jr Jul 22, 2026
2a44057
Merge remote-tracking branch 'origin/streaming-pipeline-async-core' i…
prk-Jr Jul 22, 2026
2ca4de5
Merge remote-tracking branch 'origin/feat/ssat-render-inline-creative…
prk-Jr Jul 22, 2026
be16686
Merge remote-tracking branch 'origin/trace-auction-winners-to-creativ…
prk-Jr Jul 22, 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
12 changes: 11 additions & 1 deletion crates/trusted-server-adapter-axum/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use trusted_server_core::settings::Settings;
use trusted_server_core::settings_data::{
default_config_key, default_config_store_name, get_settings_from_config_store,
};
use trusted_server_core::trace_cookie::handle_trace_mode;

use trusted_server_core::platform::RuntimeServices;

Expand Down Expand Up @@ -255,6 +256,7 @@ enum NamedRouteHandler {
/// Legacy `/admin/keys/*` aliases — denied locally with 404 so they never
/// reach the publisher fallback (which would leak admin credentials).
LegacyAdminDenied,
TraceMode,
Auction,
PageBids,
FirstPartyProxy,
Expand All @@ -279,7 +281,7 @@ const LEGACY_ADMIN_DENY_METHODS: &[Method] = &[
Method::DELETE,
];

fn named_routes() -> [NamedRoute; 12] {
fn named_routes() -> [NamedRoute; 13] {
[
NamedRoute {
path: "/.well-known/trusted-server.json",
Expand Down Expand Up @@ -320,6 +322,11 @@ fn named_routes() -> [NamedRoute; 12] {
primary_methods: LEGACY_ADMIN_DENY_METHODS,
handler: NamedRouteHandler::LegacyAdminDenied,
},
NamedRoute {
path: "/_ts/trace",
primary_methods: &[Method::GET],
handler: NamedRouteHandler::TraceMode,
},
NamedRoute {
path: "/auction",
primary_methods: &[Method::POST],
Expand Down Expand Up @@ -389,6 +396,9 @@ fn named_route_handler(
Ok(resp)
}
NamedRouteHandler::LegacyAdminDenied => Ok(legacy_admin_alias_denied()),
NamedRouteHandler::TraceMode => {
handle_trace_mode(&state.settings, req.uri().query())
}
NamedRouteHandler::Auction => {
// Build the geo-aware EC context so the auction consent
// gate sees the caller's jurisdiction — `EcContext::default()`
Expand Down
10 changes: 10 additions & 0 deletions crates/trusted-server-adapter-cloudflare/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use trusted_server_core::request_signing::{
handle_trusted_server_discovery, handle_verify_signature,
};
use trusted_server_core::settings::Settings;
use trusted_server_core::trace_cookie::handle_trace_mode;

use crate::middleware::{AuthMiddleware, FinalizeResponseMiddleware};
use crate::platform::build_runtime_services;
Expand Down Expand Up @@ -461,6 +462,15 @@ fn build_router(state: &Arc<AppState>) -> RouterService {
.post("/_ts/admin/keys/deactivate", |_ctx: RequestContext| async {
Ok::<Response, EdgeError>(admin_key_management_not_supported())
})
// Render-trace toggle: arms/disarms the ts-trace cookie and
// redirects to `/`. Gated by [debug] trace_route_enabled (404 when
// off).
.get(
"/_ts/trace",
make_handler(Arc::clone(&state), |s, _services, req| async move {
handle_trace_mode(&s.settings, req.uri().query())
}),
)
.post(
"/auction",
make_handler(Arc::clone(&state), |s, services, req| async move {
Expand Down
58 changes: 58 additions & 0 deletions crates/trusted-server-adapter-fastly/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
//! | GET | `/_ts/api/v1/identify` | [`handle_identify`] |
//! | GET | `/_ts/set-tester` | [`handle_set_tester`] |
//! | GET | `/_ts/clear-tester` | [`handle_clear_tester`] |
//! | GET | `/_ts/trace` | [`handle_trace_mode`] |
//! | OPTIONS | `/_ts/api/v1/identify` | [`cors_preflight_identify`] |
//! | POST | `/auction` | [`handle_auction`] |
//! | GET | `/first-party/proxy` | [`handle_first_party_proxy`] |
Expand Down Expand Up @@ -128,6 +129,7 @@ use trusted_server_core::settings_data::{
default_config_key, default_config_store_name, get_settings_from_config_store,
};
use trusted_server_core::tester_cookie::{handle_clear_tester, handle_set_tester};
use trusted_server_core::trace_cookie::handle_trace_mode;

use crate::middleware::{AuthMiddleware, FinalizeResponseMiddleware};
use crate::platform::{
Expand Down Expand Up @@ -587,6 +589,7 @@ async fn run_named_route(
}
NamedRouteHandler::SetTester => handle_set_tester(&state.settings),
NamedRouteHandler::ClearTester => handle_clear_tester(&state.settings),
NamedRouteHandler::TraceMode => handle_trace_mode(&state.settings, req.uri().query()),
NamedRouteHandler::Auction => {
// The auction reads consent data, so the consent KV store must be
// available — fail closed with 503 when it is configured but
Expand Down Expand Up @@ -992,6 +995,7 @@ enum NamedRouteHandler {
Identify,
SetTester,
ClearTester,
TraceMode,
Auction,
PageBids,
FirstPartyProxy,
Expand Down Expand Up @@ -1073,6 +1077,11 @@ const NAMED_ROUTES: &[NamedRoute] = &[
primary_methods: &[Method::GET],
handler: NamedRouteHandler::ClearTester,
},
NamedRoute {
path: "/_ts/trace",
primary_methods: &[Method::GET],
handler: NamedRouteHandler::TraceMode,
},
NamedRoute {
path: "/auction",
primary_methods: &[Method::POST],
Expand Down Expand Up @@ -1744,6 +1753,55 @@ mod tests {
);
}

#[test]
fn dispatch_trace_route_is_disabled_by_default() {
let router = test_router();
let response = route(&router, empty_request(Method::GET, "/_ts/trace"));

assert_eq!(
response.status(),
StatusCode::NOT_FOUND,
"disabled trace route should return 404"
);
assert!(
response.headers().get(header::SET_COOKIE).is_none(),
"disabled trace route should not set a cookie"
);
}

#[test]
fn dispatch_trace_route_arms_cookie_and_redirects() {
let mut settings = test_settings();
settings.debug.trace_route_enabled = true;
let state = app_state_for_settings(settings);
let router = TrustedServerApp::routes_for_state(&state);
let response = route(&router, empty_request(Method::GET, "/_ts/trace"));

assert_eq!(
response.status(),
StatusCode::FOUND,
"enabled trace route should redirect to root"
);
assert_eq!(
response
.headers()
.get(header::LOCATION)
.and_then(|v| v.to_str().ok()),
Some("/"),
"trace route should redirect to /"
);
let set_cookie = response
.headers()
.get(header::SET_COOKIE)
.expect("should set trace cookie")
.to_str()
.expect("should render set-cookie as utf-8");
assert!(
set_cookie.starts_with("ts-trace=1;"),
"trace route should arm the ts-trace cookie"
);
}

#[test]
fn dispatch_set_tester_sets_cookie_on_configured_domain() {
let mut settings = test_settings();
Expand Down
20 changes: 10 additions & 10 deletions crates/trusted-server-adapter-fastly/src/backend.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use core::fmt::Write as _;
use std::time::Duration;

use error_stack::{Report, ResultExt as _};
Expand Down Expand Up @@ -69,11 +70,11 @@ fn spec_digest_hex(canonical: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(canonical.as_bytes());
let digest = hasher.finalize();
digest
.iter()
.take(SPEC_DIGEST_HEX_LEN / 2)
.map(|byte| format!("{byte:02x}"))
.collect()
let mut hex = String::with_capacity(SPEC_DIGEST_HEX_LEN);
for byte in digest.iter().take(SPEC_DIGEST_HEX_LEN / 2) {
write!(hex, "{byte:02x}").expect("should write hex digit to string");
}
hex
}

/// Default first-byte timeout for backends (15 seconds).
Expand Down Expand Up @@ -327,11 +328,10 @@ impl<'a> BackendConfig<'a> {

/// Ensure a dynamic backend exists for this configuration and return its name.
///
/// The backend name is derived from the scheme, host, port, certificate
/// setting, `first_byte_timeout`, and `between_bytes_timeout` to avoid
/// collisions. Different timeout values produce different backend
/// registrations so that a tight deadline cannot be silently widened by an
/// earlier registration.
/// The name is a collision-resistant function of the complete backend spec
/// (see `Self::compute_name`), so different specs — for example, different
/// timeout values — always produce different backend registrations and a
/// tight deadline cannot be silently widened by an earlier registration.
///
/// # Errors
///
Expand Down
19 changes: 19 additions & 0 deletions crates/trusted-server-adapter-fastly/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,13 @@ const SUB_QUANTUM_LADDER_MS: [u32; 4] = [200, 150, 100, 50];
/// the greatest rung no larger than the remaining budget, and anything above
/// the top rung clamps to it. Rounding down never extends a transport cap past
/// the remaining budget.
///
/// The rung spacing trades transport window for cardinality: just below a rung
/// the haircut approaches the gap to the rung beneath (worst case ~50%, e.g. a
/// remaining budget of 9,999 ms snaps to 5,000 ms). This is accepted — on the
/// mediator path this value is the effective bound, but a denser ladder would
/// buy back at most half a bucket of transport time at the cost of
/// proportionally more backend names.
const TRANSPORT_TIMEOUT_COARSE_LADDER_MS: [u32; 8] =
[2000, 3000, 5000, 10000, 20000, 30000, 45000, 60000];

Expand Down Expand Up @@ -1130,6 +1137,12 @@ mod tests {
"canonical value {value}ms (from remaining {remaining}ms) is neither a quantum \
multiple nor a ladder rung"
);
// The mediator </body> hold bound relies on canonicalization never
// extending a transport cap past the wall-clock budget.
assert!(
value <= remaining,
"canonical value {value}ms must not extend past the remaining {remaining}ms budget"
);
}
assert!(
distinct.len() <= 16,
Expand Down Expand Up @@ -1160,6 +1173,12 @@ mod tests {
"canonical value {value}ms (from remaining {remaining}ms) is neither a quantum \
multiple nor a ladder rung"
);
// The mediator </body> hold bound relies on canonicalization never
// extending a transport cap past the wall-clock budget.
assert!(
value <= remaining,
"canonical value {value}ms must not extend past the remaining {remaining}ms budget"
);
}
// A budget above the top coarse rung must clamp to it, not open a new
// bucket per 250ms step.
Expand Down
20 changes: 19 additions & 1 deletion crates/trusted-server-adapter-spin/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use trusted_server_core::request_signing::{
handle_trusted_server_discovery, handle_verify_signature,
};
use trusted_server_core::settings::Settings;
use trusted_server_core::trace_cookie::handle_trace_mode;

use crate::middleware::{AuthMiddleware, FinalizeResponseMiddleware, NormalizeMiddleware};
use crate::platform::build_runtime_services;
Expand Down Expand Up @@ -141,14 +142,15 @@ const LEGACY_ADMIN_DENY_METHODS: &[Method] = &[
Method::DELETE,
];

fn named_fallback_paths() -> [(&'static str, &'static [Method]); 12] {
fn named_fallback_paths() -> [(&'static str, &'static [Method]); 13] {
[
("/.well-known/trusted-server.json", &[Method::GET]),
("/verify-signature", &[Method::POST]),
("/_ts/admin/keys/rotate", &[Method::POST]),
("/_ts/admin/keys/deactivate", &[Method::POST]),
("/admin/keys/rotate", LEGACY_ADMIN_DENY_METHODS),
("/admin/keys/deactivate", LEGACY_ADMIN_DENY_METHODS),
("/_ts/trace", &[Method::GET]),
("/auction", &[Method::POST]),
("/__ts/page-bids", &[Method::GET, Method::OPTIONS]),
("/first-party/proxy", &[Method::GET]),
Expand Down Expand Up @@ -541,6 +543,21 @@ fn build_router(state: &Arc<AppState>) -> RouterService {
}
};

// GET /_ts/trace — render-trace toggle: arms/disarms the ts-trace
// cookie and redirects to `/`. Gated by [debug] trace_route_enabled
// (404 when off).
let s = Arc::clone(&state);
let trace_mode_handler = move |ctx: RequestContext| {
let s = Arc::clone(&s);
async move {
let req = ctx.into_request();
Ok::<Response, EdgeError>(
handle_trace_mode(&s.settings, req.uri().query())
.unwrap_or_else(|e| http_error(&e)),
)
}
};

// GET /__ts/page-bids — SPA re-auction endpoint.
let s = Arc::clone(&state);
let page_bids_handler = move |ctx: RequestContext| {
Expand Down Expand Up @@ -730,6 +747,7 @@ fn build_router(state: &Arc<AppState>) -> RouterService {
// credentials and key-management payloads to the origin.
.post("/_ts/admin/keys/rotate", admin_not_supported_handler)
.post("/_ts/admin/keys/deactivate", admin_not_supported_handler)
.get("/_ts/trace", trace_mode_handler)
.post("/auction", auction_handler)
.get("/__ts/page-bids", page_bids_handler)
.route(
Expand Down
Loading
Loading