@@ -4,9 +4,13 @@ use error_stack::{Report, ResultExt};
44use fastly:: { Request , Response } ;
55
66use crate :: auction:: formats:: AdRequest ;
7+ use crate :: consent:: gate_eids_by_consent;
8+ use crate :: ec:: eids:: { resolve_partner_ids, to_eids} ;
79use crate :: ec:: kv:: KvIdentityGraph ;
10+ use crate :: ec:: partner:: PartnerStore ;
811use crate :: ec:: EcContext ;
912use crate :: error:: TrustedServerError ;
13+ use crate :: openrtb:: Eid ;
1014use crate :: settings:: Settings ;
1115
1216use super :: formats:: { convert_to_openrtb_response, convert_tsjs_to_auction_request} ;
@@ -29,7 +33,8 @@ use super::AuctionOrchestrator;
2933pub async fn handle_auction (
3034 settings : & Settings ,
3135 orchestrator : & AuctionOrchestrator ,
32- _kv : Option < & KvIdentityGraph > ,
36+ kv : Option < & KvIdentityGraph > ,
37+ partner_store : Option < & PartnerStore > ,
3338 ec_context : & EcContext ,
3439 mut req : Request ,
3540) -> Result < Response , Report < TrustedServerError > > {
@@ -58,10 +63,22 @@ pub async fn handle_auction(
5863 } ;
5964 let consent_context = ec_context. consent ( ) . clone ( ) ;
6065
66+ // Resolve partner EIDs from the KV identity graph when the user has
67+ // a valid EC and both KV and partner stores are available.
68+ let eids = resolve_auction_eids ( kv, partner_store, ec_context) ;
69+
6170 // Convert tsjs request format to auction request
62- let auction_request =
71+ let mut auction_request =
6372 convert_tsjs_to_auction_request ( & body, settings, & req, consent_context, ec_id) ?;
6473
74+ // Apply consent gating to the resolved EIDs before attaching them to the
75+ // auction request. `gate_eids_by_consent` checks TCF Purpose 1 + 4.
76+ let had_eids = eids. as_ref ( ) . is_some_and ( |v| !v. is_empty ( ) ) ;
77+ auction_request. user . eids = gate_eids_by_consent ( eids, auction_request. user . consent . as_ref ( ) ) ;
78+ if had_eids && auction_request. user . eids . is_none ( ) {
79+ log:: debug!( "Auction EIDs stripped by TCF consent gating" ) ;
80+ }
81+
6582 // Create auction context
6683 let context = AuctionContext {
6784 settings,
@@ -86,5 +103,126 @@ pub async fn handle_auction(
86103 ) ;
87104
88105 // Convert to OpenRTB response format with inline creative HTML
89- convert_to_openrtb_response ( & result, settings, & auction_request)
106+ convert_to_openrtb_response ( & result, settings, & auction_request, ec_context. ec_allowed ( ) )
107+ }
108+
109+ /// Resolves partner EIDs from the KV identity graph for bidstream decoration.
110+ ///
111+ /// Returns `None` when any prerequisite is missing (no KV store, no partner
112+ /// store, no EC, consent denied). On KV or partner-resolution errors, logs a
113+ /// warning and returns empty EIDs so the auction can proceed in degraded mode.
114+ fn resolve_auction_eids (
115+ kv : Option < & KvIdentityGraph > ,
116+ partner_store : Option < & PartnerStore > ,
117+ ec_context : & EcContext ,
118+ ) -> Option < Vec < Eid > > {
119+ let kv = kv?;
120+ let partner_store = partner_store?;
121+
122+ if !ec_context. ec_allowed ( ) {
123+ return None ;
124+ }
125+
126+ let ec_hash = ec_context. ec_hash ( ) ?;
127+
128+ let entry = match kv. get ( ec_hash) {
129+ Ok ( Some ( ( entry, _generation) ) ) => entry,
130+ Ok ( None ) => return Some ( Vec :: new ( ) ) ,
131+ Err ( err) => {
132+ log:: warn!( "Auction KV read failed for EC hash '{ec_hash}': {err:?}" ) ;
133+ return Some ( Vec :: new ( ) ) ;
134+ }
135+ } ;
136+
137+ match resolve_partner_ids ( partner_store, & entry) {
138+ Ok ( resolved) => Some ( to_eids ( & resolved) ) ,
139+ Err ( err) => {
140+ log:: warn!( "Auction partner resolution failed: {err:?}" ) ;
141+ Some ( Vec :: new ( ) )
142+ }
143+ }
144+ }
145+
146+ #[ cfg( test) ]
147+ mod tests {
148+ use super :: * ;
149+ use crate :: consent:: jurisdiction:: Jurisdiction ;
150+ use crate :: consent:: types:: ConsentContext ;
151+
152+ fn make_ec_context ( jurisdiction : Jurisdiction , ec_value : Option < & str > ) -> EcContext {
153+ EcContext :: new_for_test (
154+ ec_value. map ( str:: to_owned) ,
155+ ConsentContext {
156+ jurisdiction,
157+ ..ConsentContext :: default ( )
158+ } ,
159+ )
160+ }
161+
162+ #[ test]
163+ fn resolve_auction_eids_returns_none_without_kv ( ) {
164+ let partner_store = PartnerStore :: new ( "test_store" ) ;
165+ let ec_id = format ! ( "{}.ABC123" , "a" . repeat( 64 ) ) ;
166+ let ec_context = make_ec_context ( Jurisdiction :: NonRegulated , Some ( & ec_id) ) ;
167+
168+ let result = resolve_auction_eids ( None , Some ( & partner_store) , & ec_context) ;
169+ assert ! ( result. is_none( ) , "should return None when KV is missing" ) ;
170+ }
171+
172+ #[ test]
173+ fn resolve_auction_eids_returns_none_without_partner_store ( ) {
174+ let kv = KvIdentityGraph :: new ( "test_store" ) ;
175+ let ec_id = format ! ( "{}.ABC123" , "a" . repeat( 64 ) ) ;
176+ let ec_context = make_ec_context ( Jurisdiction :: NonRegulated , Some ( & ec_id) ) ;
177+
178+ let result = resolve_auction_eids ( Some ( & kv) , None , & ec_context) ;
179+ assert ! (
180+ result. is_none( ) ,
181+ "should return None when partner store is missing"
182+ ) ;
183+ }
184+
185+ #[ test]
186+ fn resolve_auction_eids_returns_none_when_consent_denied ( ) {
187+ let kv = KvIdentityGraph :: new ( "test_store" ) ;
188+ let partner_store = PartnerStore :: new ( "test_store" ) ;
189+ let ec_id = format ! ( "{}.ABC123" , "a" . repeat( 64 ) ) ;
190+ let ec_context = make_ec_context ( Jurisdiction :: Unknown , Some ( & ec_id) ) ;
191+
192+ let result = resolve_auction_eids ( Some ( & kv) , Some ( & partner_store) , & ec_context) ;
193+ assert ! (
194+ result. is_none( ) ,
195+ "should return None when consent is denied"
196+ ) ;
197+ }
198+
199+ #[ test]
200+ fn resolve_auction_eids_returns_none_when_no_ec ( ) {
201+ let kv = KvIdentityGraph :: new ( "test_store" ) ;
202+ let partner_store = PartnerStore :: new ( "test_store" ) ;
203+ let ec_context = make_ec_context ( Jurisdiction :: NonRegulated , None ) ;
204+
205+ let result = resolve_auction_eids ( Some ( & kv) , Some ( & partner_store) , & ec_context) ;
206+ assert ! (
207+ result. is_none( ) ,
208+ "should return None when no EC value is present"
209+ ) ;
210+ }
211+
212+ #[ test]
213+ fn resolve_auction_eids_returns_empty_on_kv_miss ( ) {
214+ let kv = KvIdentityGraph :: new ( "nonexistent_store" ) ;
215+ let partner_store = PartnerStore :: new ( "nonexistent_store" ) ;
216+ let ec_id = format ! ( "{}.ABC123" , "a" . repeat( 64 ) ) ;
217+ let ec_context = make_ec_context ( Jurisdiction :: NonRegulated , Some ( & ec_id) ) ;
218+
219+ // KV store doesn't exist, so the get() call will error — should return
220+ // empty Vec (degraded mode), not None.
221+ let result = resolve_auction_eids ( Some ( & kv) , Some ( & partner_store) , & ec_context) ;
222+ let eids = result. expect ( "should return Some on KV error (degraded mode)" ) ;
223+ assert ! (
224+ eids. is_empty( ) ,
225+ "should return empty vec on KV error (degraded mode)"
226+ ) ;
227+ }
90228}
0 commit comments