From ece42a022d904ed965f291a252d2c36c7e42ab25 Mon Sep 17 00:00:00 2001
From: Christian
Date: Wed, 15 Jul 2026 21:10:41 -0500
Subject: [PATCH 1/9] Add first-class APS OpenRTB integration
---
CHANGELOG.md | 2 +
TESTING.md | 5 +-
.../trusted-server-core/src/auction/README.md | 51 +-
.../src/auction/formats.rs | 185 +-
.../src/auction/orchestrator.rs | 179 +-
.../src/auction/telemetry.rs | 3 +
.../trusted-server-core/src/auction/types.rs | 138 +-
.../src/creative_opportunities.rs | 31 +-
.../src/integrations/adserver_mock.rs | 202 +-
.../src/integrations/aps.rs | 2280 ++++-----
.../src/integrations/mod.rs | 4 +
.../src/integrations/prebid.rs | 60 +-
crates/trusted-server-core/src/openrtb.rs | 14 +-
crates/trusted-server-core/src/publisher.rs | 4301 ++---------------
.../README.md | 1 +
.../browser/tests/shared/aps-renderer.spec.ts | 614 +++
.../configs/trusted-server.integration.toml | 7 +-
.../trusted-server-js/lib/src/core/auction.ts | 99 +-
.../trusted-server-js/lib/src/core/request.ts | 10 +-
.../trusted-server-js/lib/src/core/types.ts | 42 +-
.../lib/src/integrations/aps/render.ts | 301 ++
.../lib/src/integrations/gpt/index.ts | 47 +-
.../lib/test/core/auction.test.ts | 94 +
.../lib/test/core/request.test.ts | 102 +
.../lib/test/fixtures/aps-renderer-v1.json | 18 +
.../lib/test/integrations/aps/render.test.ts | 295 ++
.../lib/test/integrations/gpt/ad_init.test.ts | 189 +-
docs/guide/auction-orchestration.md | 150 +-
docs/guide/configuration.md | 5 +-
docs/guide/integrations/aps.md | 454 +-
docs/roadmap.md | 12 +-
...-15-aps-openrtb-first-class-integration.md | 897 ++++
...03-19-auction-orchestration-flow-design.md | 6 +
...-openrtb-first-class-integration-design.md | 839 ++++
trusted-server.example.toml | 6 +-
35 files changed, 5978 insertions(+), 5665 deletions(-)
create mode 100644 crates/trusted-server-integration-tests/browser/tests/shared/aps-renderer.spec.ts
create mode 100644 crates/trusted-server-js/lib/src/integrations/aps/render.ts
create mode 100644 crates/trusted-server-js/lib/test/fixtures/aps-renderer-v1.json
create mode 100644 crates/trusted-server-js/lib/test/integrations/aps/render.test.ts
create mode 100644 docs/superpowers/plans/2026-07-15-aps-openrtb-first-class-integration.md
create mode 100644 docs/superpowers/specs/2026-07-15-aps-openrtb-first-class-integration-design.md
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5bc49c80b..28a4e4806 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ 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.
- **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** — 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.
@@ -18,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
+- 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..c4a8c43ea 100644
--- a/TESTING.md
+++ b/TESTING.md
@@ -131,8 +131,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
diff --git a/crates/trusted-server-core/src/auction/README.md b/crates/trusted-server-core/src/auction/README.md
index dcc9e1506..fad3cb4d5 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://web.ads.aps.amazon-adsystem.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": "
hello");
- compressed.extend(gzip_encode(b"` that triggers bid injection lives in the SECOND gzip
- // member. `flate2::read::GzDecoder` decodes only the first member, so
- // this buffered `Body::Once` body (the non-stream auction arm) proves
- // the multi-member decoder now reads every member.
- let mut compressed = gzip_encode(b"