Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
5b86758
Store and serve full-fidelity provider EIDs
dhruv8sh Oct 30, 2026
00fb3b6
Make every browser-driven EC write a single page-load write
dhruv8sh Oct 30, 2026
3f36d00
Collect identity provider lookups on publisher navigations
dhruv8sh Nov 1, 2026
b64bd15
Fix provider ownership, orphan attribution and size cap issues
dhruv8sh Nov 2, 2026
49b73b2
Skip partner token length checks in deploy-time provider validation
dhruv8sh Nov 2, 2026
9e2a2d7
Stop warning about unregistered provider kinds where kinds are unknown
dhruv8sh Nov 2, 2026
c559b89
Write orphan-recovery lookups to the replacement EC row
dhruv8sh Nov 2, 2026
4ab0a1c
Keep responses whose headers arrive in an expired deadline snapshot
dhruv8sh Nov 2, 2026
e43ea9a
Surface arrived bodies once in an expired deadline snapshot
dhruv8sh Nov 2, 2026
dfd160a
Fall back to browser IDs for provider-owned sources the provider did …
dhruv8sh Nov 2, 2026
1b84e3c
Use an example host in the identify record test
dhruv8sh Nov 2, 2026
7db9e88
Send sec-fetch-mode navigate from the EC integration test client
dhruv8sh Nov 2, 2026
7c35af4
Skip identity lookups without EC consent
dhruv8sh Nov 3, 2026
2de6d0d
Raise the UID length limit to 1024 bytes and drop oversized IDs on KV…
dhruv8sh Nov 3, 2026
8320c3f
Merge branch 'main' into feat/s2s-eid-collection-core
dhruv8sh Nov 4, 2026
ff5113d
Gate TCF vendor consent only where TCF applies and skip lookups on un…
dhruv8sh Nov 3, 2026
f0e2fb0
Mint a new EC only on navigations that may write its row
dhruv8sh Nov 3, 2026
97e6806
Reject batch sync records for provider-owned sources
dhruv8sh Nov 1, 2026
665f8d3
Skip pull sync for provider-owned sources
dhruv8sh Nov 3, 2026
c4684f6
Document navigation-only browser EID writes and the narrowed TCF vend…
dhruv8sh Nov 3, 2026
09cad52
Skip the snapshot preload for a freshly generated EC
dhruv8sh Nov 3, 2026
17072f4
Build the partner registry once per app state
dhruv8sh Nov 3, 2026
54079ae
Remove EID resolution, create-if-absent and proxy inputs left unused
dhruv8sh Nov 3, 2026
a987581
Track the row size incrementally while enforcing the size cap
dhruv8sh Nov 3, 2026
e681663
Reuse the core clock for lookups and use should-style expect messages
dhruv8sh Nov 3, 2026
ef7ad61
Collect provider IDs after the response and keep provider refresh state
dhruv8sh Nov 4, 2026
d45b508
Never store browser UIDs for provider-owned sources
dhruv8sh Nov 5, 2026
e171d1d
Hand refresh state only to the provider that wrote it
dhruv8sh Nov 5, 2026
cbe3a2b
Remember a provider opt-out so the provider is not called again for 3…
dhruv8sh Nov 5, 2026
ca915db
Add an empty owned-entries view for providers with nothing stored
dhruv8sh Nov 5, 2026
97a69f1
Run after-response lookups only for an existing consented row
dhruv8sh Nov 5, 2026
a6adc26
Re-read the row just before the after-response write
dhruv8sh Nov 5, 2026
eb2c868
Drop provider refresh state that is already due
dhruv8sh Nov 5, 2026
aade0ff
Document collect timing, the after-response budget and rate limit gra…
dhruv8sh Nov 6, 2026
49ec524
Generate IDs only for due sources without a live UID
dhruv8sh Nov 6, 2026
dbb9fa6
Reuse the partner registry and core clock for after-response lookups
dhruv8sh Nov 6, 2026
1826524
Hide the body and context from provider response debug output
dhruv8sh Nov 6, 2026
bc65fb9
Plan no row write when pruning is the only change
dhruv8sh Nov 6, 2026
b334e75
Test after-response write conflicts and a row withdrawn before the write
dhruv8sh Nov 6, 2026
d83509c
Address review of server-side identity provider collection
dhruv8sh Nov 10, 2026
f844a52
Add design spec for server-side identity provider EID collection
dhruv8sh Nov 10, 2026
79cfe3e
Keep opt-outs, stored records and over-cap rows safe in the write pla…
dhruv8sh Nov 11, 2026
6890304
Send provider lookups only for an EC whose consented row exists
dhruv8sh Nov 11, 2026
ea83a92
Test the identity phases through the real adapter path and pass the r…
dhruv8sh Nov 11, 2026
95dc52e
Reject enabled providers at deploy time and skip disabled providers' …
dhruv8sh Nov 11, 2026
bccf8d5
Skip the after-response re-read when no lookup can change the row
dhruv8sh Nov 11, 2026
fb2b569
Correct stale comments on snapshots, tombstones and opt-out memory
dhruv8sh Nov 11, 2026
e789617
Keep a disabled provider's vendor-consent gate on its stored UIDs
dhruv8sh Nov 11, 2026
c6397be
Bound provider ext nesting to 32 levels
dhruv8sh Nov 11, 2026
bcd4733
Restore main's direct tests of the withdrawal tombstone
dhruv8sh Nov 11, 2026
a52b512
Bring operator docs in line with minting, withdrawal and provider rules
dhruv8sh Nov 11, 2026
dfc52bc
Correct spec drift and add the review log
dhruv8sh Nov 11, 2026
c6e5c99
Keep adapter test KV hashes clear of the ec_kv prefix-count test
dhruv8sh Nov 11, 2026
4089505
Drop client EIDs of a provider source whose vendor consent is withheld
dhruv8sh Nov 11, 2026
9ffdd46
Cap ts-eids cookie UIDs at 512 bytes again
dhruv8sh Nov 11, 2026
eaa1f1f
Tie the provider startup tests to the error they expect
dhruv8sh Nov 11, 2026
e17c0b2
Give Ec::default the identity budget defaults
dhruv8sh Nov 11, 2026
56146e3
Restore the completion-marker tombstone test
dhruv8sh Nov 11, 2026
4e7a6e0
Describe page-write logging and add a compatibility section to the spec
dhruv8sh Nov 11, 2026
4f946fc
Trim an over-cap legacy row first so the triggering change lands
dhruv8sh Nov 12, 2026
31837a8
Restore main's direct tombstone tests
dhruv8sh Nov 12, 2026
e6b22d2
Skip the after-response re-read for a remembered opt-out with nothing…
dhruv8sh Nov 12, 2026
c92c1d3
Remove orphan lookup retargeting that dispatch made unreachable
dhruv8sh Nov 12, 2026
224d361
Report a lookup skipped for an EC without a readable row as skipped_n…
dhruv8sh Nov 12, 2026
467ffa0
Test the client-EID vendor filter at both auction call sites
dhruv8sh Nov 12, 2026
4d95ea2
Correct stale comments and tidy test layout
dhruv8sh Nov 12, 2026
d4c838d
Document the vendor gate scope, deploy-time rejection and withdrawal …
dhruv8sh Nov 12, 2026
1ec3be7
Correct remaining spec drift and add the round-3 review entry
dhruv8sh Nov 12, 2026
7a291be
Add the round-4 review entry to the S2S EID spec
dhruv8sh Nov 12, 2026
92ae178
Gate a reassigned source on every configured provider's vendor consent
dhruv8sh Nov 12, 2026
6719f19
Add the round-5 review entry to the S2S EID spec
dhruv8sh Nov 12, 2026
a25ef71
Add the round-6 review entry to the S2S EID spec
dhruv8sh Nov 12, 2026
22f7045
Merge remote-tracking branch 'origin/main' into feat/s2s-eid-after-re…
dhruv8sh Nov 12, 2026
1600781
Mark the test ts-eids cookie Secure
dhruv8sh Nov 12, 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: 2 additions & 2 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ test_details = "test --target aarch64-apple-darwin"
# (native) are simply not listed.
build-fastly = "build -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-js -p trusted-server-openrtb --target wasm32-wasip1"
check-fastly = "check -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-js -p trusted-server-openrtb --target wasm32-wasip1"
clippy-fastly = "clippy -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-js -p trusted-server-openrtb --all-targets --all-features --target wasm32-wasip1 -- -D warnings"
test-fastly = "test -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-js -p trusted-server-openrtb --target wasm32-wasip1"
clippy-fastly = "clippy -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-identity-providers -p trusted-server-js -p trusted-server-openrtb --all-targets --all-features --target wasm32-wasip1 -- -D warnings"
test-fastly = "test -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-identity-providers -p trusted-server-js -p trusted-server-openrtb --target wasm32-wasip1"
# Feature-on counterpart of `test-fastly`. `test-fastly` does NOT pass
# --all-features, so without this the reusable-sandbox code is linted by
# `clippy-fastly` but its tests never execute.
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ crates/
trusted-server-adapter-cloudflare/ # Cloudflare Workers entry point (wasm32-unknown-unknown binary)
trusted-server-adapter-spin/ # Fermyon Spin entry point (wasm32-wasip1 component)
trusted-server-cli/ # Host-target `ts` operator CLI
trusted-server-identity-providers/ # Server-side identity provider kinds (used by the Fastly adapter)
trusted-server-js/ # TypeScript/JS build — per-integration IIFE bundles
lib/ # TS source, Vitest tests, esbuild pipeline
```
Expand Down
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
measured from dispatch, including delayed headers and trickling bodies on all
adapters. Complete available responses survive a slow publisher origin;
unfinished providers time out and mediation is skipped after budget exhaustion.
Provider bids that are already complete at the final nonblocking snapshot remain
Provider bids that are already complete at the final snapshot remain
eligible even after `tmax`; mediation starts only with remaining budget and
rejects responses collected after its cutoff.
Fastly abandons unfinished requests; Axum, Cloudflare, and Spin cancel owned work.
On Fastly the final snapshot may wait up to 1 ms per partially received body so
the host can surface bytes that already arrived, and reads on into the body of
a response whose headers arrived by the cutoff instead of abandoning it.
- Added server-side identity providers (`[[ec.identity_providers]]`, Fastly only). Enabled providers look up a visitor's IDs on publisher navigations within `ec.identity_budget_ms` (1–1000, default 200), or after the response within `ec.identity_after_response_budget_ms` (1–5000, default 2000) for `collect = "after_response"`. A provider owns its `sources`: batch sync rejects them with the new `provider_owned` reason, pull sync skips them, and browser IDs never fill them. Set `tcf_vendor_id` to require TCF vendor consent for lookups and for every ID of the provider's sources in bidstream, `x-ts-eids` and `/identify`, browser-sent IDs included; a disabled provider keeps that gate. See [Identity providers](docs/guide/configuration.md#identity-providers).
- Batch sync reports a new `row_full` item reason when a mapping would push the EC row past its new 16 KiB size limit, and continues with the remaining groups.
- An EC row over 16 KiB written by an earlier release loses its largest partner records on its next write that adds an ID, so the added ID still lands.
- **Breaking:** An EC is now minted, and the EC row written, only on a top-level publisher navigation: a `GET` with `Sec-Fetch-Dest: document`, `Sec-Fetch-Mode: navigate` and no `prefetch` in `Sec-Purpose`. Clients that send no Fetch Metadata get no EC, a navigation answered with a 301, 302, 303, 307 or 308 redirect mints none, integration proxies no longer mint one, and prefetches and prerenders never mint or write. Each navigation makes at most one conditional page-load write; a consent withdrawal's tombstone is still written on any request that carries the EC cookie.
- **Breaking:** Browser EIDs from the `/auction` request body are no longer stored, and `/auction` and `/_ts/page-bids` no longer write browser EIDs from `ts-eids` or `sharedId`. Those cookies are ingested on the next top-level navigation.
- **Breaking:** An EC minted on the current navigation is no longer sent as `user.id` to bidders, that navigation's auction does not carry the browser's `ts-eids`, and identity providers are not called for it, until a later navigation finds its row. Identity providers are also not called for an EC cookie whose row does not exist.
- This release registers no identity provider kind, so `ts config push` rejects an enabled `[[ec.identity_providers]]` entry and every runtime refuses to start with one; configure providers with `enabled = false` until a kind ships.
- A consent withdrawal keeps the `ts-ec` cookie while its tombstone cannot be written (after bounded CAS retries, or when the row cannot be read), so the next request carrying the cookie retries. Previously the cookie was expired first.
- **Breaking:** Axum, Cloudflare and Spin refuse to start when an enabled identity provider is configured, and Fastly refuses to start when an enabled provider names a `kind` this build does not include.
- **Breaking (rollback):** Stored provider UIDs may be up to 1024 bytes (browser, batch and pull ingest stay at 512). A binary from before this release cannot read an EC row holding a longer UID, so roll back only after such rows expire or are rewritten. An older binary also ignores the full-fidelity `eids` and the 30-day provider `opt_outs` and drops them when it rewrites a row (for example in batch or pull sync), so opted-out visitors are looked up again after rolling forward. A config blob that sets `ec.identity_providers` or a non-default identity budget cannot be loaded by an older binary; restore the defaults and re-push before rolling back.
- Explicit TS Console activation with `?ts_console=1` now sets a 30-minute diagnostics cookie instead of a browser-session cookie, independently of the mobile trace flag. Ordinary requests do not refresh it; end and explicitly enable diagnostics again to adopt the lifetime for an existing session.
- Publisher documents with diagnostics active now use private, no-store responses and strip conditional and range request headers before fetching the origin. An unexpected origin `304` becomes a private `502` because it cannot supply an instrumented document, including when mobile tracing is disabled.
- **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.
Expand Down
10 changes: 10 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"crates/trusted-server-adapter-spin",
"crates/trusted-server-cli",
"crates/trusted-server-core",
"crates/trusted-server-identity-providers",
"crates/trusted-server-integration-tests",
"crates/trusted-server-js",
"crates/trusted-server-openrtb",
Expand Down Expand Up @@ -121,6 +122,7 @@ toml_edit = "0.23.10"
tower = "0.4"
tracing = "0.1"
trusted-server-core = { path = "crates/trusted-server-core" }
trusted-server-identity-providers = { path = "crates/trusted-server-identity-providers" }
trusted-server-js = { path = "crates/trusted-server-js" }
trusted-server-openrtb = { path = "crates/trusted-server-openrtb" }
url = "2.5.8"
Expand Down
26 changes: 21 additions & 5 deletions crates/trusted-server-adapter-axum/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ fn build_state_with_services(
settings: Settings,
services: Option<RuntimeServices>,
) -> Result<Arc<AppState>, Report<TrustedServerError>> {
trusted_server_core::ec::provider::reject_enabled_providers(&settings.ec, "Axum")?;
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)?;
Expand Down Expand Up @@ -224,15 +225,12 @@ async fn dispatch_fallback(
}

if state.registry.has_route(&method, &path) {
let mut ec_context = EcContext::default();
return state
.registry
.handle_proxy(ProxyDispatchInput {
method: &method,
path: &path,
settings: &state.settings,
kv: None,
ec_context: &mut ec_context,
services,
req,
})
Expand Down Expand Up @@ -499,8 +497,7 @@ fn named_route_handler(
Ok(admin_ec_lookup_not_supported())
}
NamedRouteHandler::AdminEidsLookup => {
let partner_registry =
PartnerRegistry::from_config(&state.settings.ec.partners)?;
let partner_registry = PartnerRegistry::from_ec(&state.settings.ec)?;
handle_admin_eids_lookup(&partner_registry, &req)
}
NamedRouteHandler::LegacyAdminDenied => Ok(legacy_admin_alias_denied()),
Expand Down Expand Up @@ -746,14 +743,33 @@ fn trace_metadata(state: &AppState, request: &Request) -> TraceMetadata {
geo: None,
}
}

#[cfg(test)]
mod trace_dispatch_tests {
use super::*;

use futures::executor::block_on;
use trusted_server_core::redacted::Redacted;
use trusted_server_core::settings::TrustedClientIpConfig;
use trusted_server_core::trace::TraceTerminalResponse;

#[test]
fn enabled_identity_provider_fails_startup() {
let mut settings = trace_settings(false);
settings.ec.identity_providers =
vec![trusted_server_core::test_support::fixtures::enabled_identity_provider()];

let Err(error) = build_state_with_settings(settings) else {
panic!("should refuse to start with an enabled identity provider");
};

let message = format!("{error:?}");
assert!(
message.contains("example-provider") && message.contains("Axum"),
"should name the provider and the adapter: {message}"
);
}

fn trace_settings(enabled: bool) -> Settings {
Settings::from_toml(&format!(
r#"
Expand Down
23 changes: 19 additions & 4 deletions crates/trusted-server-adapter-cloudflare/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ fn build_state_with_services(
settings: Settings,
services: Option<RuntimeServices>,
) -> Result<Arc<AppState>, Report<TrustedServerError>> {
trusted_server_core::ec::provider::reject_enabled_providers(&settings.ec, "Cloudflare")?;
let plan = Arc::new(compile_auction_plan(&settings)?);
plan.validate_for_target(trusted_server_core::platform::AuctionTargetId::Cloudflare)?;
let orchestrator = build_orchestrator_with_plan(Arc::clone(&plan), &settings)?;
Expand Down Expand Up @@ -535,15 +536,12 @@ fn build_router(state: &Arc<AppState>) -> RouterService {
EdgeCacheHeader::CloudflareCdnCacheControl,
)
} else if state.registry.has_route(&method, &path) {
let mut ec_context = EcContext::default();
state
.registry
.handle_proxy(ProxyDispatchInput {
method: &method,
path: &path,
settings: &state.settings,
kv: None,
ec_context: &mut ec_context,
services: &services,
req,
})
Expand Down Expand Up @@ -655,7 +653,7 @@ fn build_router(state: &Arc<AppState>) -> RouterService {
.get(
"/_ts/admin/eids",
make_handler(Arc::clone(&state), |s, _services, req| async move {
let partner_registry = PartnerRegistry::from_config(&s.settings.ec.partners)?;
let partner_registry = PartnerRegistry::from_ec(&s.settings.ec)?;
handle_admin_eids_lookup(&partner_registry, &req)
}),
)
Expand Down Expand Up @@ -825,6 +823,23 @@ fn trace_metadata(state: &AppState, request: &Request) -> TraceMetadata {
mod tests {
use super::*;

#[test]
fn enabled_identity_provider_fails_startup() {
let mut settings = aps_profile_settings();
settings.ec.identity_providers =
vec![trusted_server_core::test_support::fixtures::enabled_identity_provider()];

let Err(error) = build_state_with_settings(settings) else {
panic!("should refuse to start with an enabled identity provider");
};

let message = format!("{error:?}");
assert!(
message.contains("example-provider") && message.contains("Cloudflare"),
"should name the provider and the adapter: {message}"
);
}

fn aps_profile_settings() -> Settings {
let mut settings = Settings::from_toml(
r#"
Expand Down
1 change: 1 addition & 0 deletions crates/trusted-server-adapter-fastly/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ log-fastly = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
trusted-server-core = { workspace = true }
trusted-server-identity-providers = { workspace = true }
url = { workspace = true }
urlencoding = { workspace = true }
web-time = { workspace = true }
Expand Down
Loading
Loading