Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change perBuyerSignals in generateBid() and reportWin() to JSON objects #1232

Open
ningwangpanda opened this issue Jul 23, 2024 · 5 comments

Comments

@ningwangpanda
Copy link

Currently, the perBuyerSignals in generateBid() and reportWin() are opaque values. We propose that Chrome changes the perBuyerSignals in generateBid() and reportWin() from opaque values to map-like JSON objects containing <key, value> pairs, i.e., the same format as the perBuyerSignals in auctionConfig.

const myAuctionConfig = {
  'perBuyerSignals': {'https://www.company.com': <JSON-shared>,
                      'https://www.dsp1.company.com': <JSON1>,
                      'https://www.dsp2.company.com': <JSON2>,
                      ...},
};

We will use generateBid() as an example in our proposal. The same changes apply to reportWin() as well.

Rationale

Our DSPs would like to merge perBuyerSignals owned by different buyer origins. For example, DSP1 would like to merge perBuyerSignals owned by company.com and dsp1.company.com. In the current implementation, we need to concatenate perBuyerSignals keyed by different buyer origins into a JSON array, and then pass the concatenated perBuyerSingals [<JSON-shared>, <JSON1>] to generateBid() for DSP1. Then DSP1 needs to check the type of perBuyerSignals in generateBid(). The parsing process could be error prone.

if (isArray(perBuyerSignals)) {
// new code path for the array
} else {
// old code path for the JSON serializable opaque value
}

SSPs would also like to inject a subset of the seller-specific signals into perBuyerSignals. Refer to https://github.com/InteractiveAdvertisingBureau/openrtb/pull/175/files for details. SSPs are also thinking about injecting a subset of the publisher-specific signals into perBuyerSignals, which could adopt the same mechanism as seller-specific signals. The rationale is that SSPs do not want to share some of the seller-specific and publisher-specific signals with all buyers participating in the auction, thus SSPs would like to inject those signals into perBuyerSignals instead of auctionSignals.

Proposal

We propose that Chrome changes the perBuyerSignals from opaque values to JSON objects in generateBid(), for example, from <JSON-shared> to {'https://www.company.com/': <JSON-shared>}. With this change, we do not need to concatenate opaque values into arrays and the merging process would be much cleaner. Moreover, the interface is more consistent: The perBuyerSignals in both generateBid() and auctionConfig are JSON objects. The only difference is: In auctionConfig, the perBuyerSignals are <key, value> pairs for the entire auction; in generateBid(), the perBuyerSignals are <key, value> pairs just for that interest group.

The current implementation leads to the following before and after the merge: JSON serializable opaque values vs. arrays.

  • Before the merge: <JSON1>
  • After the merge: [<JSON-shared>, <JSON1>]

With this change, the perBuyerSignals are JSON objects before and after the merge.

  • Before the merge: {'https://www.dsp1.company.com': <JSON1>}
  • After the merge: {'https://www.company.com': <JSON-shared>, 'https://www.dsp1.company.com': <JSON1>}

Options

Option 1: JSON objects containing <key, value> pairs

JavaScript objects cannot have duplicate keys. Thus we must guarantee that our keys are always unique. However, it is hard to guarantee uniqueness across DSPs and SSPs when specifying buyer signals, seller-specific signals and publisher-specific signals.

Option 2: Expand the URLs (Recommended)

The perBuyerSignals are JSON objects as in Option 1, which do not allow duplicate keys. However, we expand the URLs to allow multiple entries for the same site or origin.

  'perBuyerSignals': {'https://www.dsp1.company.com/buyer': <JSON-buyer>,
                      'https://www.ssp.com/seller': <JSON-seller>,
                      'https://www.ssp.com/publisher': <JSON-publisher>,
                      ...},

We propose that Chrome allows AdTechs to use expanded URLs as keys in the perBuyerSignals.

Option 3: (Indirectly) Allow duplicate keys

We can try to support duplicate entries in the perBuyerSignals by ourselves. For example, we can make the values arrays. It is buyers’ responsibility to handle potentially duplicate entries when calling generateBid() and reportWin().

  'perBuyerSignals': {'https://www.dsp1.company.com': [<JSON1>, <JSON-sub1>],
                      ...},

We can also read the stored old value, append the new value, and then store both the old and the new values under the same key.

We do not recommend self-supported duplicate entries.

Option 4: 2-level JSON objects

2-level JSON objects are more complicated. This approach should be unnecessary because 1-level JSON objects should be easier to handle.

  'perBuyerSignals': {'https://www.company.com': {'buyer': …, 'seller': …},
                      'https://www.dsp1.company.com': …,
                      ...},

Option 5: Change the interface

In this option, we propose to change the semantics by introducing a new parameter, for example, perBuyerSignalsMap to eventually replace perBuyerSignals. Overall, we would like to avoid changing the interface if possible. Moreover, perBuyerSignals exists in auctionConfig as well. This option introduces new inconsistencies in the interface: perBuyerSignalsMap in generateBid() vs. perBuyerSignals in auctionConfig.

Backward compatibility

Suppose we redefine the perBuyerSignals instead of adding a new parameter perBuyerSignalsMap in generateBid() and Chrome allows AdTechs to use expanded URLs as keys in the perBuyerSignals. AdTechs need to be able to detect opaque values vs. JSON objects while Chrome makes this change. We propose that Chrome adds an additional field isPerBuyerSignalsObject in the browserSignals, which is passed into generateBid(), to indicate the type of perBuyerSignals.

generateBid(interestGroup, auctionSignals, perBuyerSignals,
    trustedBiddingSignals, browserSignals, directFromSellerSignals) {
  …
}

Moreover, DSPs can set a sentinel field in their own perBuyerSignals to quickly detect the type of perBuyerSignals.

const dsp1PerBuyerSignals = {
  isPerBuyerSignalsObject: true
  …
};

function isPerBuyerSignalsJs(arg) {
  return !!arg.isPerBuyerSignalsObject;
}

reportWin()

The same proposal applies to reportWin() as well, namely, we propose to change the perBuyerSignals passed to reportWin() to JSON objects. We will use the new field isPerBuyerSignalsObject in the browserSignals to indicate the type of perBuyerSignals.

reportWin(auctionSignals, perBuyerSignals, sellerSignals, browserSignals,
    directFromSellerSignals) {
  ...
}

Sellside’s reportResult() uses auctionConfig and remains unchanged, similar to runAdAuction().

@dmdabbs
Copy link
Contributor

dmdabbs commented Jul 23, 2024

@ningwangpanda a note about the TechLab PR you referenced.
FWIW, an update is in the works as we also sought to improve 'namespacing.'
Each DSP's signals returned to their seller/SSP partner remain unrestricted (any serializable value). The response opt-in mechanism is dropped. Adtechs need to be prepared to receive a 'namespaced' object in generateBid when trading with componentSellers following this guideline. A change to Chrome requiring an object would facilitate that.

To address uniqueness, the 'buyer' and 'seller' were dropped in favor of using FQ names as PA identifies entities:

Examples to be incorporated into an amended PR:

const TLSAuctionConfigSnippet = {
  'seller': 'https://www.example-toplevelseller.com',
  'componentAuctions': [
   {
     "seller": "https://www.example-ssp.com",
     "interestGroupBuyers": [
       "https://www.example-dsp.com"
     ],
     "perBuyerSignals": {
       "https://www.example-dsp.com": {
         "https://www.example-toplevelseller.com": {}, /* top-level seller’s origin      */
         "https://www.example-ssp.com": {              /* seller’s origin                */
            "ortb2":{
               "imp":[
                  "pmp":{
                     "deals":[...]                     /* for example */
                  }
               ]
            }
         },
         "https://www.example-dsp.com": ...,           /* buyer’s origin, value from igi.igb[].pbs   */
         "www.example-publisher.com": {},              /* publisher host without scheme  */
       }
     },
     ...
   },
   ...
   ]
}

it is hard to guarantee uniqueness across DSPs and SSPs when specifying buyer signals, seller-specific signals and publisher-specific signals.

A seller/SSP also acting as 'publisher' as you illustrate is not a case we envisioned.
A publisher providing some signals for a specific buyer would do so using the top-level hostname (sans protocol) as PA supplies in browserSignals:

function generateBid(interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals, browserSignals, directFromSellerSignals) {
   let signalsFromMyOrigin        = perBuyerSignals[browserSignals.interestGroupOwner]; 
   let signalsFromComponentSeller = perBuyerSignals[browserSignals.seller];
   let signalsFromTopLevelSeller  = perBuyerSignals[browserSignals.topLevelSeller];
   let signalsFromPublisher       = perBuyerSignals[browserSignals.topWindowHostname];
}

Whatever scheme is implemented must also apply to directFromSellerSignals.perBuyerSignals.

@patmmccann
Copy link
Contributor

patmmccann commented Aug 2, 2024

I am not sure we need any browser change to achieve this goal, standards body work can achieve the same goal and is well underway

@ningwangpanda
Copy link
Author

We will use the example provided by @dmdabbs, plus our DSPs’ requests for sharing perBuyerSignals between DSP1 and DSP2. We call this namespaced approach the 2-level approach.

We recommend the 1-level approach in our original post. Expanded URLs can make it easier for DSPs and SSPs to avoid URL conflicts. For now, we assume that DSPs and SSPs have reached an agreement on buyer and seller origins without leveraging expanded URLs.

In the 1-level approach, we only store shared perBuyerSignals once.

'perBuyerSignals': {
  'https://www.example-dsp1.com': …, // buyer origin of DSP1
  'https://www.example-dsp2.com': …, // buyer origin of DSP2
  'https://www.example-dsp-shared.com': …, // buyer origin of shared pbs
  'https://www.example-toplevelseller.com': …, // top-level seller origin
  'https://www.example-ssp.com': …, // seller origin
  'https://www.example-publisher.com': …, // publisher host without scheme
}

In the 2-level approach, we need to store shared perBuyerSignals multiple times, for example, perBuyerSignals shared between DSP1 and DSP2. If some sellers would like to supply the same perBuyerSignals to DSP1 and DSP2, those perBuyerSignals will be stored twice in auctionConfig.

'perBuyerSignals': {
  // DSP1
  'https://www.example-dsp1.com': {
    'https://www.example-toplevelseller.com': …, // top-level seller origin
    'https://www.example-ssp.com': …, // seller origin
    'https://www.example-dsp1.com': …, // buyer origin of DSP1
    'https://www.example-dsp-shared.com': …, // buyer origin of shared pbs
    'https://www.example-publisher.com': …, // publisher host without scheme
  }
  // DSP2
  'https://www.example-dsp2.com': {
    'https://www.example-toplevelseller.com': …, // top-level seller origin
    'https://www.example-ssp.com': …, // seller origin
    'https://www.example-dsp2.com': …, // buyer origin of DSP2
    'https://www.example-dsp-shared.com': …, // buyer origin of shared pbs
    'https://www.example-publisher.com': …, // publisher host without scheme
  }
}

When DSPs parse perBuyerSignals, in the 1-level approach, DSP1 will receive the following perBuyerSignals in generateBid() with the given perBuyerSignalsSharing. The parsing of perBuyerSignals remains the same assuming we have changed the format of perBuyerSignals from opaque values to JSON objects containing <key, value> pairs.

'perBuyerSignals': {
  'https://www.example-dsp1.com': …, // buyer1's origin
  'https://www.example-dsp-shared.com': …, // buyer's origin, shared pbs
  'https://www.example-ssp.com': …, // seller's origin
}

'perBuyerSignalsSharing':
  {'https://www.example-dsp1.com': {'https://www.example-dsp-shared.com', 'https://www.example-ssp.com'},
  …,
}

In the 2-level approach, DSPs need to change generateBid() to deal with namespaced objects. Parsing 2-level <key, value> pairs takes a bit more effort than parsing 1-level <key, value> pairs.

@JensenPaul
Copy link
Collaborator

Our DSPs would like to merge perBuyerSignals owned by different buyer origins. For example, DSP1 would like to merge perBuyerSignals owned by company.com and dsp1.company.com. In the current implementation, we need to concatenate perBuyerSignals keyed by different buyer origins into a JSON array, and then pass the concatenated perBuyerSingals [<JSON-shared>, <JSON1>] to generateBid() for DSP1.

@ningwangpanda, could you explain more about the context where this concatenating would happen? I don't understand the context where a buyer would be merging perBuyerSignals for different buyer origins together, as each buyer's generateBid() is invoked in separate and independent JavaScript contexts without access to other buyer origins' perBuyerSignals.

@dmdabbs
Copy link
Contributor

dmdabbs commented Aug 20, 2024

@JensenPaul the "shared signals" scenario discussion started on #1211.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants