Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jobs:
test-rust:
name: cargo test
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4

Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- **Breaking:** Auction providers and bidder routes now use the configuration-first `[auction.providers.<id>]` and `[auction.bidders.<id>]` maps. The removed `[auction].providers = [...]` list and removed server fields under `[integrations.prebid]` and `[integrations.aps]` are rejected even when those integrations are disabled, and `ts config push` rejects the old shape before publication. Move PBS `server_url` to provider `endpoint`, server timeout to provider `timeout_ms`, request controls and bidder-parameter overrides to the `prebid-server` `profile_config`, notification suppression to `notifications`, and each former server bidder to an `[auction.bidders.<id>]` route. Move APS endpoint, timeout, account, inventory, debug, and creative controls to an `aps` provider and its `profile_config`. Browser Prebid settings remain under `[integrations.prebid]`; values such as timeout and debug that previously affected both browser and server behavior must now be configured for each owner. Provider endpoints must be absolute HTTPS URLs. Only bidder codes present in `[auction.bidders]` are folded into Trusted Server requests; unlisted publisher bids remain native browser demand. Provider response names now use the configured provider ID, such as `pbs-main`, instead of the legacy literal `prebid`; audit consumers that match `AuctionResponse.provider`. This schema has no mixed-version-safe deployment order: old binaries reject the maps and new binaries reject the retired fields, so activate the new binary and config blob together. Rollbacks must restore an old-schema blob together with the old binary.
- **Breaking** — Admin Basic-auth coverage now includes `GET /_ts/admin/ec`, `GET /_ts/admin/ec/{id}`, and `GET /_ts/admin/eids`. Existing configurations whose `[[handlers]]` patterns protect only the key-management endpoints now fail startup; broaden coverage before deploying, preferably with a namespace-boundary pattern such as `^/_ts/admin(?:/|$)`. Coverage of the dynamic `/_ts/admin/ec/{id}` route is no longer inferred from ID-shaped samples: the router accepts any segment after `/_ts/admin/ec/` and Basic Auth runs on the raw path before routing, so patterns anchored to the EC ID grammar (for example `^/_ts/admin/ec/[a-f0-9]{64}[.][A-Za-z0-9]{6}$`) are rejected in favor of a prefix-level matcher. Placeholder and well-known weak handler passwords (`changeme`, `password`, `admin`, `replace-with-…`) now fail startup on every handler rather than only on handlers inferred to cover an admin endpoint, because first-match-wins handler selection lets a narrow handler shadow the admin namespace.
- Prebid Server provider endpoints now normalize origin-only legacy `server_url` values to `/openrtb2/auction`. Query parameters are preserved, the canonical path loses a trailing slash, and configured non-root custom paths remain exact.
- Publisher HTML uses the browser-only `Cache-Control: private, max-age=60` policy for successful GET document responses and their `304 Not Modified` revalidations when server-side ad templates are structurally inactive, while preserving origin `private`/`no-store` policies and request-scoped bot, prefetch, or consent-denied responses. The `private` directive prevents shared caches that use `Cache-Control` from storing the document. Cookie-bearing responses using the generated inactive policy are finalized as `private, max-age=0`; CDN-specific cache headers remain unchanged and continue to control supporting CDNs independently. Set `[creative_opportunities].enabled = false` to disable publisher HTML and SPA template delivery without disabling direct `POST /auction` callers; an absent configuration, an unmatched slot, or a disabled auction also make the stack structurally inactive. An explicit `enabled = false` is not compatible with older binaries: restore the default, re-push and finalize the config before rolling back.
- **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` entries in Prebid bidder lists are logged and stripped. 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** — All auction paths now forward only a validated publisher-owned page URL as `site.page`, removing query and fragment data. APS OpenRTB omits `site.ref`; the existing Prebid Server path continues to forward the browser `Referer` as `site.ref`. Query-driven sites may lose contextual targeting and per-page reporting signals that previously came from query parameters.
Expand Down
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ ts --help

# Create local config, then edit placeholders before validation
ts config init
# Edit trusted-server.toml
# Edit trusted-server.toml. Server auctions use map-shaped
# [auction.providers.<id>] and [auction.bidders.<id>] tables.
ts config validate

# Audit a public page with Chrome/Chromium to bootstrap a draft config
Expand Down
223 changes: 96 additions & 127 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,57 @@
# Testing the Auction Orchestration System
# Testing auction orchestration

## Quick Test Summary
## Start the local server

The auction orchestration system has been integrated into the existing Prebid endpoints. You can test it right away using the Fastly local server!

## How to Test

### 1. Start the Local Server
Configure at least one reachable provider in `trusted-server.toml`, then start the
Fastly development server:

```bash
fastly compute serve
```

### 2. Test with Existing Endpoint
Provider endpoints must use HTTPS. Fastly and Viceroy also need a backend that
matches the provider host and TLS settings. For a deterministic local bidder,
use `scripts/template-cache-local-test.sh`, which creates a temporary CA and
registers the matching backend.

## Example configuration

```toml
[auction]
enabled = true
timeout_ms = 2000
mediator = "adserver_mock"

[auction.providers.pbs-main]
protocol = "openrtb-2.6"
profile = "prebid-server"
endpoint = "https://prebid.example.com/openrtb2/auction"
routing = "explicit"

The `/auction` endpoint now uses the orchestrator when `auction.enabled = true` in config.
[auction.providers.aps-main]
protocol = "openrtb-2.6"
profile = "aps"
endpoint = "https://aps.example.com/e/pb/bid"
routing = "all_eligible"
profile_config = { account_id = "example-aps-account", debug = false }

[auction.bidders.example-server]
provider = "pbs-main"

[integrations.adserver_mock]
enabled = true
endpoint = "https://mediator.example.com/mediate"
timeout_ms = 500
```

Replace the example endpoints and profile values before running the server.
Omit `mediator` to test local highest-bid selection without mediation.

## Send a routed request

The PBS provider uses explicit routing, so the request must include params for a
bidder listed in `[auction.bidders]`:

**Test Request:**
```bash
curl -X POST http://localhost:7676/auction \
-H "Content-Type: application/json" \
Expand All @@ -28,7 +63,15 @@ curl -X POST http://localhost:7676/auction \
"banner": {
"sizes": [[728, 90], [970, 250]]
}
}
},
"bids": [
{
"bidder": "example-server",
"params": {
"placement": "example-header-placement"
}
}
]
},
{
"code": "sidebar",
Expand All @@ -42,144 +85,70 @@ curl -X POST http://localhost:7676/auction \
}'
```

### 3. What You'll See

**With Orchestrator Enabled** (`auction.enabled = true`):
- Logs showing: `"Using auction orchestrator"`
- Parallel execution of APS OpenRTB and Prebid Server
- Optional mock-adserver mediation selecting winning bids
- Final response with winning creatives
The first impression routes to `pbs-main` and `aps-main`. The second routes only
to `aps-main` because APS uses `all_eligible` and PBS uses `explicit`.

**With Orchestrator Disabled** (`auction.enabled = false`):
- Logs showing: `"Using legacy Prebid flow"`
- Direct Prebid Server call (backward compatible)
## Check current logs

##Configuration

Edit `trusted-server.toml` to customize the auction:

```toml
# Enable/disable orchestrator
[auction]
enabled = true
providers = ["prebid", "aps"]
mediator = "adserver_mock" # If set: mediation, if omitted: highest bid wins
timeout_ms = 2000
Startup logs report plan-backed construction and the provider count:

# 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
account_id = "example-account"
timeout_ms = 800
debug = false

[integrations.adserver_mock]
enabled = true
endpoint = "http://localhost:6767/adserver/mediate"
timeout_ms = 500
```text
Building plan-backed auction orchestrator
Auction orchestrator built with 2 bidder providers
```

## Test Scenarios
A launched request logs the configured provider ID, predicted backend, and
budget. Collection logs the pending and immediate response counts:

### Scenario 1: Parallel + Mediation (Default)
**Config:**
```toml
[auction]
enabled = true
providers = ["prebid", "aps"]
mediator = "adserver_mock" # Mediator configured = parallel mediation strategy
```text
Dispatching bid request to 'pbs-main' (backend: ..., budget: ...ms)
Dispatching bid request to 'aps-main' (backend: ..., budget: ...ms)
Dispatched 2 SSP request(s) with 0 immediate response(s) (timeout: ...ms)
```

**Expected Flow:**
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
Exact backend names and budgets depend on the adapter and remaining auction
deadline. Provider failures are isolated and appear in response metadata under
the configured provider ID.

### Scenario 2: Parallel Only (No Mediation)
**Config:**
```toml
[auction]
enabled = true
providers = ["prebid", "aps"]
# No mediator = parallel only strategy
```
## Disabled auction

**Expected Flow:**
1. Prebid and APS run in parallel
2. Highest bid wins automatically
3. No mediation
Set:

### Scenario 3: Legacy Mode (Backward Compatible)
**Config:**
```toml
[auction]
enabled = false
```

**Expected Flow:**
- Original Prebid-only behavior
- No orchestration overhead

## Debugging
`POST /auction` returns an immediate no-bid response, emits an
`auction_disabled` skipped telemetry event, and performs no provider or mediator
work. The request log is:

### Check Logs
The orchestrator logs extensively:
```text
/auction: auction is disabled; returning no-bid response
```
INFO: Using auction orchestrator
INFO: Running auction with strategy: parallel_mediation
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 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
Look for these log messages on startup:
```
INFO: Registering auction provider: prebid
INFO: Registering auction provider: aps
INFO: Registering auction provider: adserver_mock
```

### Common Issues

**Issue:** `"Provider 'aps' not registered"`
**Fix:** Make sure `[integrations.aps]` is configured in `trusted-server.toml`

**Issue:** `"No providers configured"`
**Fix:** Make sure `providers = ["prebid", "aps"]` is set in `[auction]`

**Issue:** Tests fail with WASM errors
**Explanation:** Async tests don't work in WASM test environment. Integration tests via HTTP work fine!
## Automated checks

## Next Steps
Use the repository aliases instead of bare `cargo test --workspace`:

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
```bash
cargo test-fastly
cargo test-axum
cargo test-cloudflare
cargo test-spin
```

## Provider Behavior
For browser integration tests:

### APS (Amazon)
- 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
```bash
cd crates/trusted-server-js/lib
npx vitest run
```

### AdServer Mock
- Acts as mediator by calling mocktioneer's mediation endpoint
- Selects winning bids based on highest CPM
- Response time varies based on mocktioneer instance
The template-cache harness exercises plan compilation, HTTPS backend naming,
provider dispatch, mediation, and both ESI and inline delivery modes:

### Prebid
- **Real implementation** - makes actual HTTP calls
- Queries configured SSPs
- Returns real bids from real bidders
- Response time: varies (network dependent)
```bash
./scripts/template-cache-local-test.sh esi
./scripts/template-cache-local-test.sh inline
```
10 changes: 7 additions & 3 deletions crates/trusted-server-adapter-axum/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use edgezero_core::http::{
use edgezero_core::router::RouterService;
use error_stack::Report;
use trusted_server_core::auction::endpoints::handle_auction;
use trusted_server_core::auction::{AuctionOrchestrator, build_orchestrator};
use trusted_server_core::auction::{
AuctionOrchestrator, build_orchestrator_with_plan, compile_auction_plan,
};
use trusted_server_core::cache_policy::EdgeCacheHeader;
use trusted_server_core::ec::EcContext;
use trusted_server_core::ec::admin::{
Expand Down Expand Up @@ -74,8 +76,10 @@ fn build_state() -> Result<Arc<AppState>, Report<TrustedServerError>> {
fn build_state_with_settings(
settings: Settings,
) -> Result<Arc<AppState>, Report<TrustedServerError>> {
let orchestrator = build_orchestrator(&settings)?;
let registry = IntegrationRegistry::new(&settings)?;
let plan = Arc::new(compile_auction_plan(&settings)?);
plan.validate_for_target(trusted_server_core::platform::AuctionTargetId::Axum)?;
let orchestrator = build_orchestrator_with_plan(Arc::clone(&plan), &settings)?;
let registry = IntegrationRegistry::with_plan(&settings, plan)?;

Ok(Arc::new(AppState {
settings: Arc::new(settings),
Expand Down
Loading