Trace winning auction bids to rendered creatives on the page#922
Open
prk-Jr wants to merge 3 commits into
Open
Trace winning auction bids to rendered creatives on the page#922prk-Jr wants to merge 3 commits into
prk-Jr wants to merge 3 commits into
Conversation
Mint a trace tuple (auction_id, slot_id, bidder, bid/creative IDs,
adm_hash) server-side and carry it end-to-end on both render paths so a
creative in the DOM can be joined back to the winning bid in server
logs.
Server: Bid gains crid and bid_id (extracted from PBS, restored through
the mock mediator); adm_trace_hash() computes a 16-hex SHA-256 prefix of
the creative markup; build_bid_map emits hb_auction_id/hb_crid/
hb_adm_hash for SSAT and SPA page-bids; /auction responses carry
bid.ext.ts = {auction_id, adm_hash} and pass through upstream crid and
bid id with the legacy synthetic fallback; the orchestrator logs one
"auction winner:" line per winning bid on both the sync and dispatched
collect paths.
Client: new core/trace.ts writes a per-slot render registry to
window.tsjs.renders, stamps slot elements and iframes with data-ts-*
attributes (removing stale ones on re-render), and fires a
tsjs:adRendered CustomEvent. renderCreativeInline, the GPT
slotRenderEnded handler, and the pbRender bridge all record and stamp;
the bridge captures the slot element at message-receipt time so an SPA
navigation during a PBS Cache fetch cannot stamp the new route's slot
with stale data.
ChristianPavilonis
added a commit
that referenced
this pull request
Jul 17, 2026
…tives # Conflicts: # crates/trusted-server-core/src/auction/formats.rs # crates/trusted-server-core/src/auction/orchestrator.rs # crates/trusted-server-core/src/auction/telemetry.rs # crates/trusted-server-core/src/auction/types.rs # crates/trusted-server-core/src/integrations/adserver_mock.rs # crates/trusted-server-core/src/integrations/aps.rs # crates/trusted-server-core/src/integrations/prebid.rs # crates/trusted-server-core/src/publisher.rs # crates/trusted-server-js/lib/src/core/auction.ts # crates/trusted-server-js/lib/src/core/request.ts # crates/trusted-server-js/lib/src/core/types.ts # crates/trusted-server-js/lib/src/integrations/gpt/index.ts
4 tasks
Introduce an operator-armed render-trace overlay so the winning auction recorded in window.tsjs.renders can be confirmed visually on the page, on both the SSAT/GAM and /auction paths. Server: GET /_ts/trace toggles a host-only ts-trace cookie and redirects to /, gated by the new [debug] trace_route_enabled flag (404 when off). Registered on all four adapters (Fastly/Cloudflare/Axum/Spin) and documented in trusted-server.example.toml. Client: while the cookie is armed, TSJS draws a floating Google-Publisher-Console-style panel summarising every traced slot and a confirmation badge on each genuinely-rendered creative. The panel reports honest per-slot status — ok / hidden / gam-only / empty — derived from separate signals (gamEmpty from GAM's slotRenderEnded, injected = whether TS actually placed the creative, visible = ancestor-aware visibility) so it never overclaims a render GAM merely fired. Clicking a row copies the full record; the badge is shown only on ok slots.
The prod page runs two independent auctions against the same placements: the one-time SSAT auction on navigation, and an ongoing client-side /auction driven by GAM refresh through the Prebid.js trustedServer adapter. Only the SSAT path was traced, so every Prebid render was invisible to the overlay. Instrument the /auction path at its authoritative render signal. The adapter now forwards the server-side trace tuple to Prebid as meta.tsAuctionId / meta.tsAdmHash, and a bidWon listener records an `auction`-path entry (servedFrom: prebid) keyed by the ad-unit code with that auction's own ID, hash and visibility. Only bids carrying meta.tsAuctionId — i.e. the trustedServer seat — are traced, so a client-side bidder's render is never attributed to Trusted Server. The listener only observes and stamps; it does not touch Prebid's rendering. Also fix an overclaim in the SSAT path: with inject_adm_for_testing off there is no adm to inject, which left `injected` unset and let panelStatus fall through to `ok` — claiming a confirmed TS render for a slot TS had merely targeted. Targeting-only renders now report `injected: false`, and `ok` requires an explicit confirmed placement so any future path that omits the signal degrades to gam-only rather than silently claiming success.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
(auction_id, slot_id, bidder, bid_id, crid, adm_hash)— server-side and carries it end-to-end.creative_opportunities) and SPA/__ts/page-bidsnow injecthb_auction_id/hb_crid/hb_adm_hashper slot;/auctionresponses carrybid.ext.ts = {auction_id, adm_hash}and pass through the upstreamcrid/bidid(with the legacy synthetic fallback when absent).data-ts-*attributes, keeps a per-slot registry atwindow.tsjs.renders, and fires atsjs:adRenderedCustomEvent — so a creative in the DOM joins back to the server-sideauction winner:/auction delivered creative:log lines by inspection, and e2e tooling can await and assert renders. Theadm_hash(16-hex SHA-256 prefix of the delivered markup) enables byte-exact content verification on the/auctioniframe path and theinject_adm_for_testingpath.Changes
crates/trusted-server-core/src/auction/types.rsBid.cridandBid.bid_id(serde-default, skip-if-none); addadm_trace_hash()andBid::creative_trace_hash()with testscrates/trusted-server-core/src/auction/mod.rsadm_trace_hashcrates/trusted-server-core/src/auction/orchestrator.rsauction winner:trace line per winning bid on the sync (run_auction) and dispatched-collect pathscrates/trusted-server-core/src/auction/formats.rs/auctionbids carryext.ts = {auction_id, adm_hash}(hash of post-sanitize/rewrite markup), pass through upstreamcrid/bididwith legacy fallback;auction delivered creative:info log; testscrates/trusted-server-core/src/publisher.rsbuild_bid_mapemitshb_auction_id/hb_crid/hb_adm_hash; auction ID threaded throughwrite_bids_to_state(both streaming call sites) andhandle_page_bids;debug_bidgainsbid_id/crid; testscrates/trusted-server-core/src/integrations/prebid.rscridand bididfrom PBS bid responses; testscrates/trusted-server-core/src/integrations/adserver_mock.rscrid/bid_idfrom the original SSP bid when parsing mediation responsescrates/trusted-server-core/src/integrations/aps.rsBidfields set toNone(APS carries no creative pre-decode)crates/trusted-server-core/src/auction/telemetry.rsBidfieldscrates/trusted-server-js/lib/src/core/trace.tswindow.tsjs.rendersregistry,data-ts-*stamping (stale attributes removed on re-render),tsjs:adRenderedeventcrates/trusted-server-js/lib/src/core/auction.tsparseAuctionResponseretainsauctionId(bidext.tsfirst, top-levelidfallback) andadmHashcrates/trusted-server-js/lib/src/core/request.tsrenderCreativeInlinestamps container + iframe and records renders (includingrendered:falsefor rejected/missing-slot)crates/trusted-server-js/lib/src/core/types.tsAuctionBidDatatrace fields,RenderRecord,TsjsApi.renders, debug-bid mirror fieldscrates/trusted-server-js/lib/src/integrations/gpt/index.tsslotRenderEndedstamps + records; pbRender bridge recordsdebug-adm/pbs-cacheserves with the slot element captured at message-receipt time (SPA-navigation safe); debug adm iframe markedcrates/trusted-server-js/lib/test/core/trace.test.tscrates/trusted-server-js/lib/test/core/auction.test.tsext.tsprecedence testscrates/trusted-server-js/lib/test/core/request.test.tscrates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.tsslotRenderEndedstamping/registry test incl. empty-render and re-render countCloses
Closes #920
Test plan
cargo test-fastly && cargo test-axum(alsocargo test-cloudflare && cargo test-spinand the integration parity suite)cargo clippy-fastly && cargo clippy-axum(alsoclippy-cloudflare,clippy-cloudflare-wasm,clippy-spin-native,clippy-spin-wasm)cargo fmt --all -- --checkcd crates/trusted-server-js/lib && npx vitest run(415 passed)cd crates/trusted-server-js/lib && npm run formatcd docs && npm run formatcargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1fastly compute serveadm_trace_hash, bid-map trace fields,/auctionext.ts/crid/bid-id passthrough, PBS crid/bid-id extraction, and JS trace registry/stamping on both render pathsChecklist
unwrap()in production code — useexpect("should ...")tracingmacros (notprintln!)