Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 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
889159c
Add design spec for server-side identity provider EID collection
dhruv8sh Nov 10, 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
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,20 @@ 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 keeps a response whose
headers arrived by the cutoff.
Comment on lines +22 to +23

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏ This overstates the snapshot change (the wording came from our round-1 comment)

A response whose headers arrived but whose body is still incomplete after the 1 ms surface is still abandoned (deadline.rs:285-288). The change only stops abandoning it before its body is read. The same sentence is in auction-orchestration.md:832 and configuration.md:2554; there are suggestions on both. Prettier-clean.

Suggested change
the host can surface bytes that already arrived, and keeps a response whose
headers arrived by the cutoff.
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 gate both lookups and the provider's stored IDs in bidstream and `/identify` on TCF vendor consent. 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 size limit, and continues with the remaining groups.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏ The 16 KiB row cap is new for every writer but appears in no operator doc

Main had no cap. Same change suggested for api-reference.md:368-369. Prettier-clean.

Suggested change
- Batch sync reports a new `row_full` item reason when a mapping would push the EC row past its size limit, and continues with the remaining groups.
- 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.

- **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, integration proxies and `/auction` 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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏ A redirected first navigation now mints no EC, and /auction never minted one

On main a first-visit navigation answered with a 301/302/303/307/308 created the row and set ts-ec; now it gets neither (probe: status=302 row_created=false set_cookie=false). And main had only two mint call sites, the publisher fallback and integration proxies, so "/auction no longer mint one" implies a change that didn't happen. Same fix suggested for edge-cookies.md:45. Prettier-clean.

Suggested change
- **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, integration proxies and `/auction` 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:** 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, and identity providers are not called for it, until a later navigation finds its row.
- **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. 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
7 changes: 2 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
6 changes: 2 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
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