Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,7 @@ src/*.html
# leftover local build artifacts (node_modules, target, dist) that remain on disk.
/crates/js/
/crates/integration-tests/

# Wrangler config generated by the Cloudflare integration-test harness from
# wrangler.toml at run time; regenerated on every run.
wrangler.integration.generated.toml
Comment thread
jevansnyc marked this conversation as resolved.
Comment thread
jevansnyc marked this conversation as resolved.

This file was deleted.

32 changes: 27 additions & 5 deletions crates/trusted-server-core/src/access_telemetry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,13 @@ pub struct AccessTelemetrySnapshot {
///
/// Column names match spec section 9 exactly. Phase columns come from
/// `timings` and serialize as JSON `null` for phases that were never
/// recorded; every dimension column comes from `snapshot` and is a
/// non-nullable string (callers are expected to substitute an `unknown`
/// sentinel rather than leave a dimension empty). There is no `event_date`
/// column: the datasource's sorting key derives the date via
/// `toDate(event_ts)`.
/// recorded, as do the auction timeline columns (including `auction_id`,
/// whose datasource column is `Nullable(UUID)` so it joins natively against
/// `auction_events_raw.auction_id`). Every dimension column comes from
/// `snapshot` and is a non-nullable string (callers are expected to
/// substitute an `unknown` sentinel rather than leave a dimension empty).
/// There is no `event_date` column: the datasource's sorting key derives the
/// date via `toDate(event_ts)`.
#[must_use]
pub fn access_event_row(
snapshot: &AccessTelemetrySnapshot,
Expand Down Expand Up @@ -260,6 +262,10 @@ pub fn access_event_row(
"stream_ms": timings.stream_ms,
"request_elapsed_ms": timings.request_elapsed_ms,
"resp_bytes": timings.resp_bytes,
"auction_dispatched_ms": timings.auction_dispatched_ms,
"auction_resolved_ms": timings.auction_resolved_ms,
"auction_committed_ms": timings.auction_committed_ms,
"auction_id": timings.auction_id.map(|id| id.to_string()),
"template_cache_state": snapshot.template_cache_state,
"country": snapshot.country,
"ts_version": snapshot.ts_version,
Expand Down Expand Up @@ -427,6 +433,10 @@ mod tests {
"stream_ms",
"request_elapsed_ms",
"resp_bytes",
"auction_dispatched_ms",
"auction_resolved_ms",
"auction_committed_ms",
"auction_id",
] {
assert!(
parsed[field].is_null(),
Expand All @@ -452,6 +462,10 @@ mod tests {
);
}
assert_eq!(parsed["auction_wait_placement"], "none");
assert!(
row.contains("\"auction_id\":null"),
"auction_id should serialize as an explicit null key, not be omitted: {row}"
);
}

#[test]
Expand All @@ -470,6 +484,10 @@ mod tests {
stream_ms: Some(8),
auction_wait_placement: Some(AuctionWaitPlacement::InStream),
resp_bytes: Some(1024),
auction_dispatched_ms: Some(9),
auction_resolved_ms: Some(10),
auction_committed_ms: Some(11),
auction_id: Some(uuid::uuid!("33333333-3333-3333-3333-333333333333")),
};
let row = access_event_row(&snapshot, &timings, 1_700_000_000_000);
let parsed: serde_json::Value =
Expand All @@ -479,6 +497,10 @@ mod tests {
assert_eq!(parsed["stream_ms"], 8);
assert_eq!(parsed["resp_bytes"], 1024);
assert_eq!(parsed["auction_wait_placement"], "in_stream");
assert_eq!(parsed["auction_dispatched_ms"], 9);
assert_eq!(parsed["auction_resolved_ms"], 10);
assert_eq!(parsed["auction_committed_ms"], 11);
assert_eq!(parsed["auction_id"], "33333333-3333-3333-3333-333333333333");
}

#[test]
Expand Down
24 changes: 23 additions & 1 deletion crates/trusted-server-core/src/auction/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::ec::registry::PartnerRegistry;
use crate::error::TrustedServerError;
use crate::openrtb::{Eid, Uid};
use crate::platform::RuntimeServices;
use crate::request_timing::RequestTimings;
use crate::settings::Settings;

use super::AuctionOrchestrator;
Expand Down Expand Up @@ -145,6 +146,16 @@ pub async fn handle_auction(
}

let (parts, body) = req.into_parts();
// T0-anchored timeline (spec section 18). This route is the auction, so
// dispatch and resolve bracket `run_auction` rather than the origin
// fetch, and the commit mark lands once the OpenRTB response carrying the
// targeting has been built. A defaulted handle records into nothing that
// is ever read, so direct-handler tests are unaffected.
let timings = parts
.extensions
.get::<RequestTimings>()
.cloned()
.unwrap_or_default();
Comment thread
ChristianPavilonis marked this conversation as resolved.
let body_bytes = body.into_bytes().unwrap_or_default();
if body_bytes.len() > MAX_AUCTION_BODY_SIZE {
return Response::builder()
Expand Down Expand Up @@ -318,10 +329,17 @@ pub async fn handle_auction(
ec_context,
);

timings.set_auction_id(observation.auction_id);
Comment thread
ChristianPavilonis marked this conversation as resolved.

// Run the auction
timings.mark_auction_dispatched();
Comment thread
ChristianPavilonis marked this conversation as resolved.
let result = match orchestrator.run_auction(&auction_request, &context).await {
Ok(result) => result,
Ok(result) => {
timings.mark_auction_resolved();
result
}
Err(err) => {
timings.mark_auction_resolved();
let elapsed_ms = observation.elapsed_ms();
emit_auction_events_best_effort_lazy(services, || {
build_auction_events(
Comment thread
ChristianPavilonis marked this conversation as resolved.
Expand Down Expand Up @@ -366,6 +384,10 @@ pub async fn handle_auction(
}
};

// Targeting is available to the caller: for this route the response body
// is the commit, since there is no page state to write into.
timings.mark_auction_committed();

emit_auction_events_best_effort_lazy(services, || {
build_auction_events(
observation,
Expand Down
60 changes: 60 additions & 0 deletions crates/trusted-server-core/src/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4000,6 +4000,8 @@ async fn collect_non_html_auction(
params
.timings
.record_auction_wait(placement, wait_started.elapsed());
// T0-anchored timeline mark (spec section 18): final bid or timeout.
params.timings.mark_auction_resolved();
Comment thread
jevansnyc marked this conversation as resolved.
let delivered_winner_slots = write_bids_to_state(
&result.winning_bids,
params.price_granularity,
Expand All @@ -4009,6 +4011,9 @@ async fn collect_non_html_auction(
settings.debug.inject_adm_for_testing,
auction_id.as_deref(),
);
// T0-anchored timeline mark (spec section 18): winning bids are in page
// state, available to the response pipeline.
params.timings.mark_auction_committed();
if let (Some(observation), Some(auction_request)) =
(telemetry.observation, telemetry.auction_request.as_ref())
{
Expand Down Expand Up @@ -4059,6 +4064,8 @@ async fn collect_stream_auction(
.collect_dispatched_auction(dispatched, services, &collect_ctx)
.await;
timings.record_auction_wait(*placement, wait_started.elapsed());
// T0-anchored timeline mark (spec section 18): final bid or timeout.
timings.mark_auction_resolved();
Comment thread
aram356 marked this conversation as resolved.
log::info!(
"body_close_hold_loop: collect complete - {} winning bid(s)",
result.winning_bids.len()
Expand All @@ -4072,6 +4079,9 @@ async fn collect_stream_auction(
settings.debug.inject_adm_for_testing,
auction_id.as_deref(),
);
// T0-anchored timeline mark (spec section 18): winning bids are in page
// state, available to the response pipeline.
timings.mark_auction_committed();
if let (Some(observation), Some(auction_request)) =
(telemetry.observation, telemetry.auction_request.as_ref())
{
Expand Down Expand Up @@ -4537,6 +4547,12 @@ pub async fn handle_publisher_request(
matched_slots.len(),
ec_context,
);
// T0-anchored timeline (spec section 18): stamp the join key here
// rather than on dispatch, because every branch below emits an
// `auction_events_raw` row under this id — completed, dispatch
// failed, and skipped alike. Stamping it on dispatch would leave the
// failed and skipped rows unjoinable.
timings.set_auction_id(observation.auction_id);

if should_run_auction {
let slots_ctx = MatchedSlotsContext {
Expand Down Expand Up @@ -4580,6 +4596,11 @@ pub async fn handle_publisher_request(
.await
{
DispatchAuctionOutcome::Dispatched(dispatched) => {
// Bid requests have left the edge. A skipped auction or a
// failed dispatch never reaches this arm, so a null
// dispatch offset next to a non-null `auction_id` reads as
// "attempted, nothing sent".
timings.mark_auction_dispatched();
auction_request_for_telemetry = Some(auction_request);
auction_observation = Some(observation);
Some(dispatched)
Expand Down Expand Up @@ -6519,6 +6540,14 @@ pub async fn handle_page_bids(
ec_context: &mut EcContext,
req: Request<EdgeBody>,
) -> Result<Response<EdgeBody>, Report<TrustedServerError>> {
// Same defaulted-handle rule as `handle_publisher_request`: a request
// without the extension records into a collector nothing reads.
let timings = req
.extensions()
.get::<RequestTimings>()
.cloned()
.unwrap_or_default();

// CSRF-style gate: refuse cross-site invocations before any other work —
// including the not-configured 404 below, which would otherwise tell a
// cross-site caller whether this deployment has creative opportunities.
Expand Down Expand Up @@ -6678,6 +6707,11 @@ pub async fn handle_page_bids(
matched_slots.len(),
ec_context,
);
// Same T0-anchored timeline as the navigation path (spec section 18).
// This route runs the auction through `run_auction` rather than the
// dispatch/collect split, so dispatch and resolve bracket that one
// call instead of the origin fetch.
timings.set_auction_id(observation.auction_id);
Comment thread
ChristianPavilonis marked this conversation as resolved.
if ad_stack_enabled && !is_bot && !is_prefetch {
let slots_ctx = MatchedSlotsContext {
matched_slots: &matched_slots,
Expand Down Expand Up @@ -6732,12 +6766,14 @@ pub async fn handle_page_bids(
provider_responses: None,
services,
};
timings.mark_auction_dispatched();
match auction
.orchestrator
.run_auction(&auction_request, &auction_context)
.await
{
Ok(result) => {
timings.mark_auction_resolved();
let winning_bids = result.winning_bids.clone();
let auction_id = diagnostics_auction_id(settings);
let bid_map = build_bid_map_with_auction_id(
Expand All @@ -6748,6 +6784,8 @@ pub async fn handle_page_bids(
settings.debug.inject_adm_for_testing,
auction_id.as_deref(),
);
// Targeting is available to the response pipeline.
timings.mark_auction_committed();
let delivered_winner_slots = bid_map.keys().cloned().collect();
emit_auction_events_best_effort_lazy(services, || {
build_auction_events(
Expand All @@ -6763,6 +6801,7 @@ pub async fn handle_page_bids(
(winning_bids, Some(bid_map))
}
Err(e) => {
timings.mark_auction_resolved();
log::warn!("page-bids auction failed: {e:?}");
let elapsed_ms = observation.elapsed_ms();
emit_auction_events_best_effort_lazy(services, || {
Expand Down Expand Up @@ -18327,6 +18366,17 @@ mod tests {
snapshot.auction_wait_ms.is_some(),
"should record an auction wait duration"
);
// The T0 marks are recorded at this same collect site, so a collect
// path that stops calling them fails here rather than silently
// emitting null columns.
assert!(
snapshot.auction_resolved_ms.is_some(),
"should mark the auction resolved at the streaming collect site"
);
assert!(
snapshot.auction_committed_ms.is_some(),
"should mark the auction committed at the streaming collect site"
);
}

#[test]
Expand Down Expand Up @@ -18400,6 +18450,16 @@ mod tests {
snapshot.auction_wait_ms.is_some(),
"should record an auction wait duration"
);
// Same guard as the streaming test: both collect sites must mark, or
// one body mode quietly reports null offsets.
assert!(
snapshot.auction_resolved_ms.is_some(),
"should mark the auction resolved at the buffered collect site"
);
assert!(
snapshot.auction_committed_ms.is_some(),
"should mark the auction committed at the buffered collect site"
);
}

#[test]
Expand Down
Loading
Loading