Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
21fd172
Add implementation plan for server-side identity providers
dhruv8sh Oct 22, 2026
91b45d4
Add identity cookie lists and a name-and-prefix cookie strip
dhruv8sh Oct 22, 2026
1028e04
Strip identity cookies from Prebid Server requests in every mode
dhruv8sh Oct 22, 2026
cf708fb
Strip ID cookies from publisher origin requests
dhruv8sh Oct 22, 2026
24780c0
Refuse the sealed identity cookies as template cache keys
dhruv8sh Oct 22, 2026
7a3fe16
Add the identity module with provider and source ID newtypes
dhruv8sh Oct 22, 2026
1dde880
Add provider metadata and provider state to the KV schema
dhruv8sh Oct 22, 2026
4c3103b
Record when browser ingest acquired each ID
dhruv8sh Oct 22, 2026
a9de291
Add the identity provider descriptor and its builder
dhruv8sh Oct 22, 2026
587fb9e
Add declared-input enforcement for identity providers
dhruv8sh Oct 22, 2026
8fcc5d9
Add provider requests, responses and resolutions
dhruv8sh Oct 22, 2026
b957cad
Add the IdentityProvider trait
dhruv8sh Oct 22, 2026
3d56fbb
Evaluate provider consent policies against decoded consent
dhruv8sh Oct 22, 2026
f642f85
Add record revisions and keyed identity hashes
dhruv8sh Oct 22, 2026
aca8ae5
Filter served KV IDs through one servability check
dhruv8sh Oct 22, 2026
2a62a1d
Add IdentityMutation and its merge into a live identity row
dhruv8sh Oct 22, 2026
2114969
Seal and read the server-owned identity cookies
dhruv8sh Oct 22, 2026
43b6e59
Encode the ts-eids-s cookie and reconcile it with KV
dhruv8sh Oct 22, 2026
9a1dd40
Add per-batch ts-eids-r removal cookies
dhruv8sh Oct 22, 2026
bb36616
Add [ec.identity] coordinator settings
dhruv8sh Oct 22, 2026
c0ee7ac
Add coordinator eligibility and outcome rules
dhruv8sh Oct 22, 2026
1de5e2c
Add coordinator dispatch with request checks and byte caps
dhruv8sh Oct 22, 2026
b43af79
Model pull sync partners as GenericPullProvider
dhruv8sh Oct 22, 2026
cd6165e
Decide the writer request once routing allows dispatch
dhruv8sh Oct 22, 2026
c9625a3
Stop writing cluster size from /identify
dhruv8sh Oct 22, 2026
0d4fba3
Queue browser ingest in the writer's mutation and stage it on stagers
dhruv8sh Oct 22, 2026
90d6663
Write overflowing removal batches before send
dhruv8sh Oct 22, 2026
be2a35b
Merge the server identity set into auction EIDs
dhruv8sh Oct 22, 2026
57a545a
Flush identity changes once after send and run pull partners there
dhruv8sh Oct 22, 2026
b3ced25
Test the one-write-per-page-load invariant end to end
dhruv8sh Oct 22, 2026
9080fb8
Remove the temporary dead-code allow from the identity module
dhruv8sh Oct 22, 2026
67ae2f0
Log identity cookie sizes and the flush outcome per request
dhruv8sh Oct 22, 2026
56ddd07
Document the single identity write and the new identity cookies
dhruv8sh Oct 22, 2026
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- A page load now writes the EC identity row at most once. Only the writer request, a real browser's top-level document navigation, writes, after the page has been sent; a failed write is not retried. `/auction`, `/_ts/page-bids` and `/_ts/api/v1/identify` no longer write KV. `/auction` stages the body EIDs that KV lacks in the new sealed `__Host-ts-eids-s` cookie, and the next navigation writes them; `/_ts/page-bids` EIDs reach KV through `ts-eids` at the next navigation. Removals and opt-outs not yet in KV travel in per-batch `__Host-ts-eids-r-*` cookies. A new visitor's row creation now carries its browser EIDs, so it stays one write.
- Pull sync partners now run as server-side identity providers. They are called after send on a returning visitor's writer navigation, from the visitor's second page load, back off per visitor on errors instead of retrying on every request, and are rate-limited per visitor instead of per client IP. `ec.pull_sync_concurrency` is deprecated in favour of `[ec.identity].max_concurrency`. Rollback: once providers write metadata, do not roll back past this release; an older reader drops the new KV fields on its next write.
- KV IDs sent to bidders, the admin view and `/_ts/api/v1/identify` now pass one servability check: expired IDs, and IDs whose writing provider is disabled, opted out or blocked by its consent policy, are never served. `/_ts/api/v1/identify` uses the request's consent, never the row's stored consent snapshot. Stored provider `atype` and UID-level `ext` are emitted.
- **Breaking:** The publisher origin no longer receives the `ts-eids` cookie, and Prebid Server no longer receives any Trusted Server identity cookie (`ts-ec`, `ts-eids`, `ts-ec-pull-complete`, and the server-owned `__Host-ts-eids-s` and `__Host-ts-eids-r-*` cookies that a later release introduces). The origin still receives `ts-ec`. Origin code that read `ts-eids` should read the `x-ts-eids` response header instead. A Prebid Server `Cookie` header that is not valid UTF-8 is now dropped instead of forwarded. This release is the rollback floor for the server-side identity provider work.
- **Breaking:** `x-ts-version` now reports the deployed git version — the release tag, else the branch, else the first 6 characters of the commit — compiled in from the build-time `TRUSTED_SERVER__GIT_VERSION` (set by the deploy pipeline) or local git, and is sent by every adapter, including on the Fastly `GET /health` probe. The Fastly service version it previously carried is now `x-ts-fastly-version`; update dashboards, monitors, and scripts that read `x-ts-version` as the Fastly version number. Builds with neither the override nor git omit the header.
- **Breaking:** `ts prebid bundle` is now `ts prebid client`, alongside the new `ts prebid server` namespace. Update scripts and runbooks to use `ts prebid client` with the same arguments. The old `bundle` spelling is no longer accepted and has no compatibility alias.
- The S2S `/_ts/api/v1/batch-sync` endpoint now validates the full batch and calls the CAS-protected update path once per distinct normalized EC ID. The last valid UID wins within a group, and infrastructure failures reject the failing and each unprocessed group, so accepted and `kv_unavailable` input indexes may interleave.
Expand Down
63 changes: 60 additions & 3 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ publish = false
version = "0.1.0"

[workspace.dependencies]
aes-gcm = "0.10"
anyhow = "1"
assert_cmd = "2"
async-stream = "0.3"
Expand Down
1 change: 1 addition & 0 deletions crates/trusted-server-adapter-axum/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ async fn dispatch_fallback(
ec_context: &mut ec_context,
services,
req,
is_real_browser: false,
})
.await
.unwrap_or_else(|| {
Expand Down
1 change: 1 addition & 0 deletions crates/trusted-server-adapter-cloudflare/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ fn build_router(state: &Arc<AppState>) -> RouterService {
ec_context: &mut ec_context,
services: &services,
req,
is_real_browser: false,
})
.await
.unwrap_or_else(|| {
Expand Down
40 changes: 27 additions & 13 deletions crates/trusted-server-adapter-fastly/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,8 @@ pub(crate) struct EcFinalizeState {
pub(crate) use_finalize_kv: bool,
pub(crate) eids_cookie: Option<String>,
pub(crate) sharedid_cookie: Option<String>,
pub(crate) is_real_browser: bool,
/// Per-request services carried to the entry point so the pull-sync
/// dispatcher can reuse the same platform HTTP client.
/// Per-request services carried to the entry point so the after-send
/// identity flush can reuse the same platform HTTP client.
pub(crate) services: RuntimeServices,
}

Expand Down Expand Up @@ -361,7 +360,6 @@ impl EcRequestState {
use_finalize_kv: self.finalize_kv_graph.is_some(),
eids_cookie: self.eids_cookie,
sharedid_cookie: self.sharedid_cookie,
is_real_browser: self.is_real_browser,
services: self.services,
}
}
Expand Down Expand Up @@ -735,8 +733,6 @@ async fn run_named_route(
/// graph + partner registry + rate limiter, with a default EC context for
/// response finalization.
fn run_batch_sync(state: &AppState, services: &RuntimeServices, req: Request) -> Response {
let device_signals = device_signals_for(&req);
let is_real_browser = device_signals.looks_like_browser();
let eids_cookie = crate::extract_cookie_value(&req, COOKIE_TS_EIDS);
let sharedid_cookie = crate::extract_cookie_value(&req, COOKIE_SHAREDID);

Expand All @@ -754,7 +750,6 @@ fn run_batch_sync(state: &AppState, services: &RuntimeServices, req: Request) ->
use_finalize_kv: false,
eids_cookie,
sharedid_cookie,
is_real_browser,
services: services.clone(),
});
response
Expand Down Expand Up @@ -820,6 +815,7 @@ async fn dispatch_fallback(
ec_context: &mut ec.ec_context,
services,
req,
is_real_browser: ec.is_real_browser,
})
.await
.unwrap_or_else(|| {
Expand Down Expand Up @@ -847,13 +843,25 @@ async fn dispatch_fallback(
if is_navigation {
ec.ec_context.set_eid_sync_source(EidSyncSource::Navigation);
}
ec.ec_context.decide_writer(&req, true, ec.is_real_browser);
let is_publisher_navigation = ec.is_real_browser && is_navigation;
if is_publisher_navigation
&& let Err(err) = ec
if is_publisher_navigation {
// A new visitor's create carries the browser EIDs, so it stays
// the page load's one KV write.
if let Ok(partner_registry) = PartnerRegistry::from_config(&state.settings.ec.partners)
{
ec.ec_context.seed_generation_from_cookies(
&partner_registry,
ec.eids_cookie.as_deref(),
ec.sharedid_cookie.as_deref(),
);
}
if let Err(err) = ec
.ec_context
.generate_if_needed(&state.settings, ec.kv_graph.as_ref())
{
log::warn!("EC generation failed for publisher proxy: {err:?}");
{
log::warn!("EC generation failed for publisher proxy: {err:?}");
}
}

// Run the server-side auction with the configured creative-
Expand Down Expand Up @@ -2417,7 +2425,10 @@ mod tests {
.get::<super::EcFinalizeState>()
.expect("fallback response should carry EcFinalizeState");
assert!(
finalize.is_real_browser,
finalize
.ec_context
.device_signals()
.is_some_and(DeviceSignals::looks_like_browser),
"browser-shaped JA4/H2 signals from the request extension must mark the request as a real browser"
);
}
Expand All @@ -2436,7 +2447,10 @@ mod tests {
.get::<super::EcFinalizeState>()
.expect("fallback response should carry EcFinalizeState");
assert!(
!finalize.is_real_browser,
!finalize
.ec_context
.device_signals()
.is_some_and(DeviceSignals::looks_like_browser),
"a request without captured device signals should not be classified as a real browser"
);
}
Expand Down
Loading
Loading