@@ -555,6 +555,13 @@ def __init__(
555555 # Monotonic per-buy revision counter (update_media_buy's
556556 # optimistic-concurrency token).
557557 self ._buy_revisions : dict [str , int ] = {}
558+ # Bidirectional buyer-creative-id ↔ upstream-creative-id map.
559+ # The upstream mints ``cr_<uuid>`` on every upload regardless of
560+ # ``client_request_id``, so the seller has to track the mapping
561+ # to (a) echo the buyer's id in ``list_creatives`` and (b)
562+ # translate before calling ``attach_creative`` upstream.
563+ self ._creative_id_map : dict [str , str ] = {} # buyer_id → upstream_id
564+ self ._creative_id_reverse : dict [str , str ] = {} # upstream_id → buyer_id
558565 # AccountStore is always wired. ``app.main`` passes the
559566 # MOCK_AD_SERVER_URL env so resolved accounts route at the JS
560567 # mock-server fixture. Tests that bypass the AccountStore (by
@@ -1111,12 +1118,17 @@ async def update_media_buy(
11111118 continue
11121119 if creative_id in seen_creative_ids :
11131120 continue
1121+ # Translate buyer's creative_id to the upstream id
1122+ # before issuing attach_creative. Pass through unchanged
1123+ # when no mapping is known (the upstream will surface
1124+ # a 404 → CREATIVE_NOT_FOUND).
1125+ upstream_creative_id = self ._creative_id_map .get (creative_id , creative_id )
11141126 await upstream_helpers .attach_creative (
11151127 client ,
11161128 network_code = network_code ,
11171129 order_id = media_buy_id ,
11181130 line_item_id = pkg_id ,
1119- creative_id = creative_id ,
1131+ creative_id = upstream_creative_id ,
11201132 )
11211133 existing_assignments .append (
11221134 ca .model_dump (mode = "json" , exclude_none = True )
@@ -1199,9 +1211,13 @@ async def sync_creatives(
11991211 snippet = getattr (creative , "snippet" , None )
12001212 if snippet is not None :
12011213 payload ["snippet" ] = str (snippet )
1202- await upstream_helpers .upload_creative (
1214+ upstream_resp = await upstream_helpers .upload_creative (
12031215 client , network_code = network_code , payload = payload
12041216 )
1217+ upstream_id = str (upstream_resp .get ("creative_id" ) or "" )
1218+ if upstream_id :
1219+ self ._creative_id_map [creative .creative_id ] = upstream_id
1220+ self ._creative_id_reverse [upstream_id ] = creative .creative_id
12051221 results .append (
12061222 SyncCreativeResult .model_validate (
12071223 {
@@ -1348,6 +1364,14 @@ async def get_media_buys(
13481364 upstream_orders = [
13491365 o for o in payload .get ("orders" , []) if o .get ("advertiser_id" ) == advertiser_id
13501366 ]
1367+ # Narrow to the requested media_buy_ids when the buyer supplied
1368+ # them. Storyboards chain get_media_buys after create with the
1369+ # captured media_buy_id; without this filter the response leaks
1370+ # every advertiser-scoped buy and the buyer's ``media_buys[0]``
1371+ # lookup hits a different scenario's order.
1372+ if getattr (req , "media_buy_ids" , None ):
1373+ wanted_ids = {str (x ) for x in req .media_buy_ids }
1374+ upstream_orders = [o for o in upstream_orders if o .get ("order_id" ) in wanted_ids ]
13511375 page = upstream_orders [offset : offset + limit ]
13521376 media_buys : list [dict [str , Any ]] = []
13531377 for order in page :
@@ -1546,7 +1570,10 @@ async def list_creatives(
15461570 page = upstream_creatives [offset : offset + limit ]
15471571 creatives = [
15481572 {
1549- "creative_id" : c ["creative_id" ],
1573+ # Surface the buyer's original creative_id when the seller
1574+ # owns the mapping; falls back to the upstream id when the
1575+ # creative was synced outside this seller instance.
1576+ "creative_id" : self ._creative_id_reverse .get (c ["creative_id" ], c ["creative_id" ]),
15501577 "name" : c ["name" ],
15511578 "format_id" : {"agent_url" : agent_url , "id" : c .get ("format_id" , "" )},
15521579 "status" : _project_creative_status (c .get ("status" , "active" )),
0 commit comments