Repository navigation
Description
Summary
EC route in the shared app, verified on Fastly: a same-origin POST to /_ts/api/v1/ec mints, records, runs the providers, takes the posted consent and withdrawals, and answers with the mirror's view as JSON; a GET answers Prebid's pixelUrl with a 204 and only re-sets an existing SharedID (reconciliation from #1535).
Part of #1558, server-side identity on every adapter. Plan ID T19, planned in wave 7 (MVP: Fastly and Axum); priority P1, size M. Design: spec; order and dependencies: plan.
Requirements
ID-9 EC route
A same-origin route, /_ts/api/v1/ec by default, lets the script Trusted Server injects into proxied pages report consent and receive identity, so a reader who consents after the page loads gets an EC on that page view instead of the next navigation. A POST whose Origin matches the page mints the EC when the reader has none and consent allows, creates the record, runs the providers as a navigation does, and sets ts-ec, the SharedID cookie and the mirror cookie on its own response. It carries the consent strings the script reads from the CMP, the TCF string from __tcfapi and the GPP string from __gpp (Sourcepoint's capture from #1535 where present), which replace the consent cookies for that request; a withdrawal expires ts-ec, the SharedID cookie and the mirror cookie and tombstones the record in that response. The POST answers with JSON holding exactly what WB-4 would put in the mirror cookie for that request, and the SharedID only under SID-2 and SID-6; the script merges it into Prebid, and nothing else in the record reaches the page. A plain GET answers Prebid's SharedID pixelUrl with an empty 204: it never mints, calls a provider or changes consent, and only re-sets an existing SharedID cookie from the record (WB-3). The script sends consent changes in order with at most one call in flight. While a call is in flight, a newer change replaces a waiting one, except a waiting withdrawal, which is never replaced: later changes wait behind it. So every withdrawal reaches the server, after any grant in progress and before any later grant. The script discards a result that arrives while a newer consent change is waiting, and a withdrawal removes the IDs it merged into Prebid. Every response carries Cache-Control: no-store; a rate limit on the route waits with ID-6 (DEC-15). The route is served from the page's own host, which WebKit's cap on cookies from non-document responses exempts when the IP matches the page's (read from WebKit's source; the Accept tests it).
WB-1 Document and EC route responses only
Identity cookies are set on top-level HTML document responses and on EC route responses (ID-9), on every supported adapter.
WB-3 SharedID cookie
When SID-2 and SID-6 allow it, the server sets the SharedID cookie from the record if the request has none or a different value. When neither the record nor the request has one, the server creates a SharedID in the format Prebid writes (a UUID v4), stores it in the record and sets it the same way, because a browser may block Prebid.js from loading at all (DEC-7). It creates and writes back a SharedID only when the configured storage.type is cookie (Prebid's cookie&html5 counts as cookie): with html5 Prebid never reads the cookie, so a server-made SharedID would split the reader's identity (SID-3). It uses Prebid's storage name and the Domain Prebid writes with, is not HttpOnly, and is Secure; SameSite=Lax for one year. It keeps Prebid's <name>_cst companion consistent, since a missing or different one makes Prebid rewrite the cookie from script, which Safari caps. Prebid's extend with a pixelUrl keeps working alongside it.
WB-4 Mirror cookie
__Host-ts-eids-mirror as in #1489: opt-in per partner (DEC-10), dropped per vendor when TCF consent is denied, needs bidstream_enabled and Purpose 4, 3,584-byte budget, host-only. Every mirrored ID expires within 30 days.
Current state
What the code does today, from the design's survey at the revisions it names:
- ID-9: on main only a real-browser navigation through the publisher proxy mints an EC, and no such route exists. Extract existing Sourcepoint consent synchronization #1535 (RC line) posts Sourcepoint's GPP string to a separate, non-minting
/_ts/consentroute, wired into each adapter's router. - WB-1: main mints
ts-econ Fastly navigations, and the RC adds Axum. - WB-3: no branch writes it.
- WB-4: in Mirror provider EIDs to a cookie and run pull sync as an identity provider #1489 such an ID never expires (
Expiry::Never); closed Add server-side identity providers for six vendors #1461 capped mirrored IDs at 24 hours.
Acceptance criteria
- ID-9: On Fastly and Axum, a consented same-origin
POSTwith nots-eccreates the record and setsts-ecand the SharedID cookie in that response - ID-9: Its JSON omits a non-mirrored partner's ID, an ID whose vendor's TCF consent is denied and an expired ID
- ID-9: A withdrawal issued while a grant call with no EC and delayed providers is in flight leaves no identity cookie, no live record and no ID in Prebid
- ID-9: With grant A in flight, a withdrawal B and then a grant C reach the server as A, B, C, so B expires the identity cookies and tombstones A's record and C is handled as a first consent with no
ts-ec - ID-9: A cross-site
Origin, aGETand a client ID-7 refuses mint nothing and call no provider - ID-9: An unchanged returning reader gets no
Set-Cookie - ID-9: In Safari,
ts-ecand the SharedID cookie set by the route keep their one-yearMax-Ageon Fastly and on Axum behind CloudFront - WB-1: Identity
Set-Cookieheaders appear on navigation and EC route responses and never on asset or other API responses - WB-3: Record present and cookie absent gives both cookies with those attributes
- WB-3: A consented first visit with no SharedID anywhere gets one created, stored and set, and a reader with
_pubcid_optoutgets none - WB-3: With
storage.type = html5the server neither creates nor sets a SharedID - WB-3: An equal cookie with a consistent
<name>_cstgives noSet-Cookie - WB-3: An equal cookie with a missing or different
<name>_cstre-sets only the companion - WB-3: Purpose 1 denied or
_pubcid_optoutexpires both - WB-3: Purpose 4 denied with Purpose 1 granted gives no
Set-Cookieand leaves the browser's cookie alone - WB-4: An ID written with no stated expiry is mirrored with an expiry of at most 30 days
Dependencies
Blocked by:
- Providers in the shared app, awaited before finalization #1591 Providers in the shared app, awaited before finalization (new in this plan)
- Refuse record creation when a trusted JA4 contradicts the User-Agent #1592 Refuse record creation when a trusted JA4 contradicts the User-Agent (new in this plan)
- Create and write back SharedID in the shared app #1593 Create and write back SharedID in the shared app (new in this plan)
- One identity app in core for every adapter #1600 One identity app in core for every adapter (new in this plan)
Blocks:
- Identity features pass on Axum from the shared app #1581 Identity features pass on Axum from the shared app (new in this plan)
- Identity features pass on Cloudflare from the shared app #1596 Identity features pass on Cloudflare from the shared app (new in this plan)
- Injected script calls the EC route on consent and merges the IDs into Prebid #1599 Injected script calls the EC route on consent and merges the IDs into Prebid (new in this plan)
Related pull requests
-
Extract existing Sourcepoint consent synchronization #1535 Extract existing Sourcepoint consent synchronization: carries part of this work.
Fixes the plan asks of Extract existing Sourcepoint consent synchronization #1535
Proposed closure, for the author to decide: Extract existing Sourcepoint consent synchronization #1535, Extract native EC lifecycle and DynamoDB persistence #1536 and Extract explicit EC storage selection and celld #1537 once T19, T20, A2 and A8 carry their code and tests.
Activity