diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index ec85b96ac..4973afe44 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -183,7 +183,16 @@ jobs: with: node-version: ${{ steps.shared-setup.outputs.node-version }} cache: npm - cache-dependency-path: crates/trusted-server-integration-tests/browser/package-lock.json + cache-dependency-path: | + crates/trusted-server-integration-tests/browser/package-lock.json + crates/trusted-server-js/lib/package-lock.json + + - name: Build TSJS browser fixtures + working-directory: crates/trusted-server-js/lib + run: | + npm ci + npm run build + npm run build:prebid-external - name: Install Playwright working-directory: crates/trusted-server-integration-tests/browser diff --git a/CHANGELOG.md b/CHANGELOG.md index f29355745..3dbd82a35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **Breaking** — Replaced the legacy APS contextual integration with APS OpenRTB at `/e/pb/bid`. APS configuration now uses canonical `account_id` (`pub_id` remains a compatibility alias), no longer requires APS-specific slot IDs, and defaults script creative eligibility off. Operators must update the endpoint, disable native APS demand for Trusted Server cohorts, and prepare GAM/Universal Creative targeting for `hb_bidder=aps` before rollout. APS renderer winners now preserve the upstream bid `id`, omit `crid` when APS omits it, and carry `ext.trusted_server.renderer` instead of `adm`; external `/auction` consumers must support this response shape. - **Breaking** — `bid_param_zone_overrides` inner values must now be JSON objects; previously non-object or empty values (`"header" = "x"`, `"header" = {}`) were accepted and silently produced a dead rule at runtime. They now fail at startup with a configuration error. Operators upgrading should audit their `bid_param_zone_overrides` config for non-object zone entries. - **Breaking** — Integration configuration strings are no longer globally reinterpreted as JSON scalars. Operators upgrading should audit `[integrations.*]` settings and use native TOML/typed-config booleans and numbers (for example, `enabled = true`, not `enabled = "true"`); quoted numeric and boolean scalars now fail validation instead of silently converting. - **Breaking** — Sourcepoint browser module inclusion now requires explicit `[integrations.sourcepoint].enabled = true`; operators relying on the previous unconditional Sourcepoint module should enable the integration before upgrading. - The SPA re-auction endpoint moved from `/__ts/page-bids` to `/_ts/page-bids`, joining every other internal route in the `/_ts/` namespace. The old path stays registered as a deprecated alias so already-loaded bundles keep serving ads, and responses on it carry a `Link: …; rel="deprecation"` header so remaining traffic is measurable from edge logs; removal is tracked in [#970](https://github.com/IABTechLab/trusted-server/issues/970). Two deployment notes: audit `[[handlers]]` for patterns broad enough to cover `/_ts` (for example `^/_ts`), which would put this browser-facing endpoint behind Basic Auth and return `401` to every visitor — scope them to `^/_ts/admin`; and prefer rolling forward over rolling back, since a server reverted past this release does not register the canonical path. In both cases the shipped client falls back to the deprecated alias, so the exposure is bounded until that alias is removed. +- Added optional APS `inventory_domain` and `inventory_page_origin` overrides for deployments whose edge hostname differs from the APS-authorized inventory identity. +- Preserved APS renderer capabilities through the client-side `trustedServer` Prebid adapter, allowing its generated `hb_adid` to render through GAM and Prebid Universal Creative instead of producing an empty creative. ### Security @@ -20,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added opt-in APS HTTP debug metadata for controlled test sites, exposing the direct request and response under `/auction` provider metadata using the Prebid Server `debug.httpcalls` shape. +- Added typed APS renderer transport for direct auctions and GAM/Prebid Universal Creative, using a minimized one-bid envelope, a fragment-bound nonce, and an opaque sandboxed renderer endpoint. - Added Osano consent mirror integration docs and public enablement guidance. - Implemented basic authentication for configurable endpoint paths (#73) - Added integrations guide with example `testlight` integration diff --git a/TESTING.md b/TESTING.md index e1ac1c3a2..e5ccba4cf 100644 --- a/TESTING.md +++ b/TESTING.md @@ -46,8 +46,8 @@ curl -X POST http://localhost:7676/auction \ **With Orchestrator Enabled** (`auction.enabled = true`): - Logs showing: `"Using auction orchestrator"` -- Parallel execution of APS (mocked) and Prebid (real) -- GAM mediation (mocked) selecting winning bids +- Parallel execution of APS OpenRTB and Prebid Server +- Optional mock-adserver mediation selecting winning bids - Final response with winning creatives **With Orchestrator Disabled** (`auction.enabled = false`): @@ -66,11 +66,13 @@ providers = ["prebid", "aps"] mediator = "adserver_mock" # If set: mediation, if omitted: highest bid wins timeout_ms = 2000 -# Mock provider configs +# APS OpenRTB provider. The built-in production endpoint is used when +# endpoint is omitted; use only an account authorized for test traffic. [integrations.aps] enabled = true -mock = true -mock_price = 2.50 +account_id = "example-account" +timeout_ms = 800 +debug = false [integrations.adserver_mock] enabled = true @@ -90,10 +92,10 @@ mediator = "adserver_mock" # Mediator configured = parallel mediation strategy ``` **Expected Flow:** -1. Prebid queries real SSPs -2. APS returns mock bids ($2.50 CPM) -3. AdServer Mock mediates between all bids -4. Winning creative returned +1. Prebid queries its configured bidders through Prebid Server +2. APS sends an OpenRTB request for eligible banner impressions +3. AdServer Mock mediates the provider responses +4. The winning creative or typed APS renderer is returned ### Scenario 2: Parallel Only (No Mediation) **Config:** @@ -131,8 +133,9 @@ INFO: Running 2 bidders in parallel INFO: Requesting bids from: prebid INFO: Prebid returned 2 bids (time: 120ms) INFO: Requesting bids from: aps -INFO: APS (MOCK): returning 2 bids in 80ms -INFO: GAM mediation: slot 'header-banner' won by 'amazon-aps' at $2.50 CPM +INFO: APS requests bids for 2 impressions +INFO: APS returns 2 accepted bids in 80ms +INFO: GAM mediation: slot 'header-banner' won by 'aps' at $2.50 CPM ``` ### Verify Provider Registration @@ -156,18 +159,19 @@ INFO: Registering auction provider: adserver_mock ## Next Steps -1. **Test with real Prebid Server** - Verify Prebid bids work correctly -2. **Implement real APS** - Replace mock with actual Amazon TAM API calls -3. **Implement real GAM** - Add Google Ad Manager API integration -4. **Add metrics** - Track bid rates, win rates, latency per provider +1. **Verify Prebid Server demand** - Confirm configured bidders return expected test bids +2. **Verify APS eligibility** - Confirm the test account, inventory identity, and `/e/pb/bid` endpoint are authorized +3. **Exercise renderer security** - Run the APS browser integration suite for iframe and script creatives +4. **Add metrics** - Track bid rates, win rates, latency, and aggregate drop reasons per provider -## Mock Provider Behavior +## Provider Behavior ### APS (Amazon) -- Returns bids for all slots -- Default mock price: $2.50 CPM -- Always returns 2 bids -- Response time: ~80ms (simulated) +- Sends real OpenRTB requests for eligible banner slots +- Safely drops malformed, unsupported, or unrenderable bids and reports aggregate reasons +- Reduces multiple APS candidates to one winner per impression +- Returns typed renderer descriptors rather than exposing `adm` outside the sandbox +- Automated tests intercept upstream traffic and use fictional response fixtures ### AdServer Mock - Acts as mediator by calling mocktioneer's mediation endpoint diff --git a/crates/trusted-server-core/src/auction/README.md b/crates/trusted-server-core/src/auction/README.md index dcc9e1506..7c5c21462 100644 --- a/crates/trusted-server-core/src/auction/README.md +++ b/crates/trusted-server-core/src/auction/README.md @@ -117,7 +117,7 @@ When a request arrives at the `/auction` endpoint, it goes through the following ▼ ┌──────────────────────────────────────────────────────────────────────┐ │ 9. Each Provider Processes Request │ -│ - Transform AuctionRequest → Provider format (e.g., APS TAM) │ +│ - Transform AuctionRequest → Provider OpenRTB request │ │ - Send HTTP request to provider endpoint │ │ - Parse provider response │ │ - Transform → AuctionResponse with Bid[] │ @@ -188,30 +188,21 @@ AdSlot { #### 3. Provider Execution Each registered provider (APS, Prebid, etc.) receives the `AuctionRequest` and: -- Transforms it to their specific format (e.g., APS TAM, OpenRTB) +- Transforms it to the provider's OpenRTB request format - Makes HTTP request to their endpoint - Parses the response - Returns `AuctionResponse` with `Bid[]` For example, APS provider: ```rust -// Transform AuctionRequest → ApsBidRequest -let aps_request = ApsBidRequest { - pub_id: "5128", - slots: vec![ - ApsSlot { - slot_id: "header-banner", - sizes: vec![[728, 90], [970, 250]], - slot_name: Some("header-banner"), - } - ], - page_url: Some("https://example.com"), - ua: Some("Mozilla/5.0..."), - timeout: Some(800), -}; - -// HTTP POST to http://localhost:6767/e/dtb/bid -// Parse response → AuctionResponse +// Transform AuctionRequest → APS OpenRTB request +// - ext.account = configured account_id +// - ext.sdk = { source: "prebid", version: "2.2.0" } +// - banner slots become secure impressions with matching formats/floors +// - existing consent, identity, device, and geo privacy gates apply + +// HTTP POST to https://aps.example.com/e/pb/bid +// Parse decoded-price response → AuctionResponse with a typed renderer ``` #### 4. Response Assembly @@ -222,17 +213,29 @@ The orchestrator collects all bids and creates an OpenRTB response: "id": "auction-response", "seatbid": [ { - "seat": "amazon-aps", + "seat": "aps", "bid": [ { - "id": "amazon-aps-header-banner", + "id": "fictional-selected-bid-id", "impid": "header-banner", "price": 2.5, - "adm": "