Skip to content

July release candidate (DO NOT MERGE) - #919

Draft
ChristianPavilonis wants to merge 361 commits into
mainfrom
rc/july
Draft

July release candidate (DO NOT MERGE)#919
ChristianPavilonis wants to merge 361 commits into
mainfrom
rc/july

Conversation

@ChristianPavilonis

@ChristianPavilonis ChristianPavilonis commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

Consolidates the July release-candidate changes for review and integration testing before the individual changes merge to main.

Included pull requests

Already included through main

Pending Review / Merge

@aram356 aram356 changed the title July release candidate July release candidate (DO NOT MERGE) Jul 16, 2026
@aram356
aram356 marked this pull request as draft July 17, 2026 15:44
aram356 and others added 24 commits July 21, 2026 11:12
…main

The navigation-path AuctionObservationContext still took its
publisher_domain from the incoming edge Host header, so telemetry rows
for initial and SPA navigations were attributed to the edge/staging host
while /auction rows and outbound bid requests used the configured
publisher domain.

Both observation constructors now use settings.publisher.domain.

Add handler-level coverage for both navigation paths: the previous
regression test called build_auction_request directly and would keep
passing if a call site reverted to request_info.host, since both sources
are &str. The new tests drive handle_publisher_request and
handle_page_bids with a divergent edge host, capture the dispatched
AuctionRequest through a recording provider, and assert both the bid
request fields and the emitted telemetry rows carry the configured
domain. Verified they fail when either the builder argument or the
observation argument is reverted.
The #937 squash on main and rc/july's copy of the same change diverged on
one line: rc/july renamed MatchedSlotsContext::request_path to
request_path_and_query. Keep rc/july's field name — request_path no
longer exists on this branch.

# Conflicts:
#	crates/trusted-server-core/src/publisher.rs
…-to-creatives

# Conflicts:
#	crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts
#	trusted-server.example.toml
HEAD Content-Length describes the corresponding GET representation, not
a body that will be buffered. Applying the buffered-response limit to
that metadata prevents valid S3 Image Optimizer preflights from
reaching their streamed GET request.

Resolves: #950
# Conflicts:
#	crates/trusted-server-adapter-fastly/src/platform.rs
#	crates/trusted-server-core/src/publisher.rs
…isolation

Creative sanitization ran unconditionally on every markup bid, stripping
`script`/`object`/`embed`/`form` and friends together with their inner content.
For script-based creatives — the majority of programmatic display — that leaves
nothing renderable, and the slot goes blank with no error: the leftover markup is
usually a tracking pixel, so the ad server reports a successful render.

Measured over 291 creative deliveries on a live publisher: a median 43% of bytes
removed, 29 creatives reduced by more than 80%, and 20 reduced below 500 bytes.
One bidder lost 100% of every creative; another lost 76% across 43 of them.

Add `auction.sanitize_creatives` so sanitization can be disabled where creatives
render in a foreign-origin frame (the Prebid Universal Creative inside the ad
server's iframe), and make both creative controls opt-in: `sanitize_creatives`
and `rewrite_creatives` now default to false, so a creative ships exactly as the
bidder returned it unless a publisher asks for processing.

Removing `allow-same-origin` from the creative iframe sandbox is part of the same
change rather than a follow-up. Sanitization was documented as "the primary
defense against malicious markup", with the sandbox as defense-in-depth — but the
sandbox granted `allow-same-origin` alongside `allow-scripts`, which removes its
origin isolation entirely. With sanitization now optional, that pairing would
leave creative markup able to reach publisher cookies, storage, and same-origin
fetches. The two sibling sandboxes (APS_RENDERER_SANDBOX, ADM_IFRAME_SANDBOX)
already omit the token for exactly this reason; this brings the third in line, so
the origin boundary no longer depends on an optional transform.

Note the default change alters behaviour for deployments that never set
`rewrite_creatives`: creative URL rewriting is now off unless enabled explicitly.

Verified end to end: creatives pass through byte-for-byte (triplelift 8902 ->
8902, openx 22069 -> 22069, previously 100% and 35% losses), page renders with
ads serving and no hydration errors.
ChristianPavilonis and others added 30 commits August 4, 2026 17:54
Carry bid_id through adserver_mock mediation. The reconstruction restored
nurl, burl, ad_id and the cache fields from the original SSP bid but
hard-coded bid_id to None, so a mediated bid whose only hb_adid source is
the OpenRTB bid id lost it and never rendered — the exact failure this
branch fixes, re-opened on the mediated path. Mediated APS bids were
worse off still: they carry no ad_id or cache_id for the restore to
recover, so they reached the page with no hb_adid at all. The mediation
response is itself OpenRTB, so prefer the mediated bid's own id and fall
back to the original SSP bid's.

Reject blank identity strings before applying the cache_id -> ad_id ->
bid_id precedence. Option::or treats Some("") as present, so a blank
cacheId or adid outranked a valid bid id and emitted an empty hb_adid —
falsey on the page, so GPT skips the targeting key and the render bridge
has nothing to match. Prebid's parse_bid likewise treats an empty
OpenRTB id as absent.

Emit hb_cache_host and hb_cache_path only alongside a real Prebid Cache
UUID. PBS reports the cache url and cacheId independently, so a bid with
coordinates but no UUID pointed the Universal Creative at
?uuid=<non-cache-id>, a guaranteed miss, instead of letting it fall
through to the inline adm.

Warn when the chosen hb_adid exceeds GAM's 40-character targeting value
limit. GAM drops an over-long value, so the creative echoes nothing and
the bridge's equality check never matches. Log rather than truncate: a
truncated id is no longer unique per bid, which is what lets one slot's
render claim another slot's creative.
Brings the PR #996 review fixes onto rc/july. The original fallback commit
was already merged; this adds the follow-ups plus two conflict
resolutions where rc/july had since moved.

hb_adid precedence keeps rc/july's renderer tier and gains blank
rejection on every tier, so a bidder emitting an empty cacheId or adid no
longer outranks a usable identifier:

    non_empty(cache_id) -> non_empty(renderer_bid_id)
      -> non_empty(ad_id) -> non_empty(bid_id)

Cache coordinates keep rc/july's placement inside the processed_adm match
and gain the cache_id requirement as a second, independent condition:
absent a UUID the coordinates would send the Universal Creative to
?uuid=<non-cache-id>.

Mediation bid_id precedence is inverted relative to the main-targeted
branch, deliberately. On main, APS bids carry no bid_id, so preferring the
mediation response's own id was the only way to give them an hb_adid. On
rc/july, aps.rs populates bid_id and a typed renderer envelope is minted
against it, and build_bid_map derives hb_adid from that pairing — so
substituting the mediator's id would key targeting to an id the renderer
does not know. The original SSP bid's id therefore wins here, with the
mediated id as the fallback for an upstream bid that carried none.
Bring the GPT delivery-evidence and refresh-attribution diagnostics onto the
July release branch. rc/july already carried a later evolution of the auction-ID
plumbing (PR #922 render tracing, the delivered-winner-slot telemetry, and the
APS renderer bridge), so the overlapping Rust and JS changes keep the rc/july
implementation and layer the new diagnostics on top of it.

Conflict resolutions:
- publisher.rs: keep the rc/july `write_bids_to_state` -> delivered winner slots
  contract and prebuilt page-bids bid map; keep both new tests; update the
  incoming test provider to the current `ProviderRequestOutcome` trait.
- gpt/index.ts: keep the render-trace and APS renderer paths and add the
  creative request/response/failure diagnostics around them. The attempt is
  recorded after the APS branch, which is served by the APS universal creative
  and has no Trusted Server creative response to resolve.
- prebid/index.ts: keep the resolved bare-refresh slot list and dispatch it
  through the diagnostics-aware refresh wrapper.
- store.ts: adopt the incoming response-based `slotOnload` correlation.
- Drop the pre-squash `gpt_diagnostics_bootstrap.js` and its test, which the
  upstream #974 squash removed as superseded by server-recognized activation.

Verified: cargo fmt, clippy (fastly/axum/cloudflare/cloudflare-wasm/spin-native/
spin-wasm), test-fastly, test-axum, test-cloudflare, test-spin, parity,
vitest (812 tests), JS and docs format.
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.

4 participants