diff --git a/FLEDGE.md b/FLEDGE.md index 10ace4016..4181b3753 100644 --- a/FLEDGE.md +++ b/FLEDGE.md @@ -30,12 +30,14 @@ See [the Protected Audience API specification](https://wicg.github.io/turtledove - [3.3 Metadata with the Ad Bid](#33-metadata-with-the-ad-bid) - [3.4 Ads Composed of Multiple Pieces](#34-ads-composed-of-multiple-pieces) - [3.5 Filtering and Prioritizing Interest Groups](#35-filtering-and-prioritizing-interest-groups) + - [3.6 Currency Checking](#36-currency-checking) - [4. Browsers Render the Winning Ad](#4-browsers-render-the-winning-ad) - [5. Event-Level Reporting (for now)](#5-event-level-reporting-for-now) - [5.1 Seller Reporting on Render](#51-seller-reporting-on-render) - [5.2 Buyer Reporting on Render and Ad Events](#52-buyer-reporting-on-render-and-ad-events) - [5.2.1 Noised and Bucketed Signals](#521-noised-and-bucketed-signals) - - [5.3 Losing Bidder Reporting](#53-losing-bidder-reporting) + - [5.3 Currencies in Reporting](#53-currencies-in-reporting) + - [5.4 Losing Bidder Reporting](#54-losing-bidder-reporting) - [6. Additional Bids](#6-additional-bids) - [6.1 Auction Nonce](#61-auction-nonce) - [6.2 Negative Targeting](#62-negative-targeting) @@ -119,9 +121,22 @@ const myGroup = { 'executionMode': ..., 'trustedBiddingSignalsURL': ..., 'trustedBiddingSignalsKeys': ['key1', 'key2'], + 'trustedBiddingSignalsSlotSizeMode' : 'slot-size', 'userBiddingSignals': {...}, - 'ads': [shoesAd1, shoesAd2, shoesAd3], - 'adComponents': [runningShoes1, runningShoes2, gymShoes, gymTrainers1, gymTrainers2], + 'ads': [{renderUrl: shoesAd1, sizeGroup: 'group1', ...}, + {renderUrl: shoesAd2, sizeGroup: 'group2', ...}, + {renderUrl: shoesAd3, sizeGroup: 'size3', ...}], + 'adComponents': [{renderUrl: runningShoes1, sizeGroup: 'group2', ...}, + {renderUrl: runningShoes2, sizeGroup: 'group2', ...}, + {renderUrl: gymShoes, sizeGroup; 'group2', ...}, + {renderUrl: gymTrainers1, sizeGroup: 'size4', ...}, + {renderUrl: gymTrainers2, sizeGroup: 'size4', ...}], + 'adSizes': {'size1': {width: '100', height: '100'}, + 'size2': {width: '100', height: '200'}, + 'size3': {width: '75', height: '25'}, + 'size4': {width: '100', height: '25'}}, + 'sizeGroups:' {'group1': ['size1', 'size2', 'size3'], + 'group2': ['size3', 'size4']}, 'auctionServerRequestFlags': ['omit-ads'], }; const joinPromise = navigator.joinAdInterestGroup(myGroup); @@ -132,7 +147,7 @@ The browser will only allow the `joinAdInterestGroup()` operation with the permi The returned `joinPromise` is resolved if the group is successfully joined, and rejected with an error if the join operation fails. The error message and the resolution time must _not_ depend on what interest groups a user is in, or any cross-origin browser state, apart from the results of the .well-known fetch, to avoid leaking any data across sites. -There is a complementary API `navigator.leaveAdInterestGroup(myGroup)` which looks only at `myGroup.name` and `myGroup.owner`. As with join calls, `leaveAdInterestGroup()` also returns a promise. As a special case to support in-ad UIs, invoking `navigator.leaveAdInterestGroup()` from inside an ad that is being targeted at a particular interest group will cause the browser to leave that group, irrespective of permission policies. Note that calling `navigator.leaveAdInterestGroup()` without arguments isn't supported inside a component ad frame. +There is a complementary API `navigator.leaveAdInterestGroup(myGroup)` which looks only at `myGroup.name` and `myGroup.owner`. As with join calls, `leaveAdInterestGroup()` also returns a promise. As a special case to support in-ad UIs, invoking `navigator.leaveAdInterestGroup()` from inside an ad that is being targeted at a particular interest group will cause the browser to leave that group, irrespective of permission policies. Note that calling `navigator.leaveAdInterestGroup()` without arguments inside a [component ad](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#34-ads-composed-of-multiple-pieces) frame isn't supported until Chrome M120. Starting from Chrome M120, calling `navigator.leaveAdInterestGroup()` without arguments inside a component ad frame is supported. The ad component frame is required to be same-origin with the interest group owner for the leave to succeed, same as calling `leaveAdInterestGroup()` without arguments in a non-ad-component frame. There is a related API `navigator.clearOriginJoinedAdInterestGroups(owner, [])` that leaves all interest groups owned by `owner` that were joined on the current top-level frame's origin, and also returns a Promise. The `[]` argument is an optional list of interest group names that will not be left, and if not present, it will act as if an empty array was passed. This method has no effect on joined interest groups owned by `owner` that were most recently joined on different top-level origins. @@ -211,6 +226,12 @@ The `ads` list contains the various ads that the interest group might show. Eac The `adComponents` field contains the various ad components (or "products") that can be used to construct ["Ads Composed of Multiple Pieces"](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#34-ads-composed-of-multiple-pieces)). Similar to the `ads` field, each entry is an object that includes a `renderURL` and optional `adRenderId`, and `metadata` fields. Thanks to `ads` and `adComponents` being separate fields, the buyer is able to update the `ads` field via the `updateURL` without losing `adComponents` stored in the interest group. +The `adSizes` field (optionally) contains a dictionary of named ad sizes. Each size has the format `{width: widthVal, height: heightVal}`, where the values can have either pixel units (e.g. `100` or `'100px'`) or screen dimension coordinates (e.g. `100sw` or `100sh`). For example, the size `{width: '100sw', height: 50}` describes an ad that is the width of the screen and 50 pixels tall. The size `{width: '100sw', height: '200sw'}` describes an ad that is the width of the screen and has a 1:2 aspect ratio. Sizes with screen dimension coordinates are primarily intended for screen-width ads on mobile devices, and may be restricted in certain contexts (to be determined) for privacy reasons. + +The `sizeGroups` field (optionally) contains a dictionary of named lists of ad sizes. Each ad declared above must specify a size group, saying which sizes it might be loaded at. Each named ad size is also considered a size group, so you don't need to manually define singleton size groups; for example see the `sizeGroup: 'size3'` code above. + +At some point in the future - no earlier than Q1 2025 - when the sizes are declared, the URL-size pairings will be used to prefetch k-anonymity checks to limit the configurations that can win an auction, please see [this doc](https://developer.chrome.com/docs/privacy-sandbox/protected-audience-api/feature-status/#k-anonymity). In the present implementation, only the URL is used for k-anonymity checks, not the size. When an ad with a particular size wins the auction (including in the current implementation), the size will be substituted into any macros in the URL (through `{%AD_WIDTH%}` and `{%AD_HEIGHT%}`, or `${AD_WIDTH}` and `${AD_HEIGHT}`), and once loaded into a fenced frame, the size will be used by the browser to freeze the fenced frame's inner dimensions. We therefore recommend using ad size declarations, but they are not required at this time. + The `auctionServerRequestFlags` field is optional and is only used for auctions [run on an auction server](https://github.com/WICG/turtledove/blob/main/FLEDGE_browser_bidding_and_auction_API.md). This field contains a list of enumerated values that change what data is sent in the auction blob: * The `omit-ads` enumeration causes the request to omit the `ads` and `adComponents` fields for @@ -227,7 +248,7 @@ same-origin with `owner` and must point to URLs whose responses include the HTTP response header `Ad-Auction-Allowed: true` to ensure they are allowed to be used for loading Protected Audience resources. -The browser will provide protection against microtargeting, by only rendering an ad if the same rendering URL is being shown to a sufficiently large number of people (e.g. at least 50 people would have seen the ad, if it were allowed to show). While in the [Outcome-Based TURTLEDOVE](https://github.com/WICG/turtledove/blob/master/OUTCOME_BASED.md) proposal this threshold applied only to the rendered creative, Protected Audience has the additional requirement that the tuple of the interest group owner, bidding script URL, and rendered creative must be k-anonymous for an ad to be shown (this is necessary to ensure the current event-level reporting for interest group win reporting is sufficiently private). For interest groups that have component ads, all of the component ads must also separately meet this threshold for the ad to be shown. Since a single interest group can carry multiple possible ads that it might show, the group will have an opportunity to re-bid another one of its ads to act as a "fallback ad" any time its most-preferred choice is below threshold. This means that a small, specialized ad that is still below the k-anonymity threshold could still choose to participate in auctions, and its interest group has a way to fall back to a more generic ad until the more specialized one has a large enough audience. +The browser will provide protection against microtargeting, by only rendering an ad if the same rendering URL is being shown to a sufficiently large number of people (e.g. at least 50 people would have seen the ad, if it were allowed to show). While in the [Outcome-Based TURTLEDOVE](https://github.com/WICG/turtledove/blob/master/OUTCOME_BASED.md) proposal this threshold applied only to the rendered creative, Protected Audience has the additional requirement that the tuple of the interest group owner, bidding script URL, and rendered creative (URL, and [no earlier than Q1 2025](https://developer.chrome.com/docs/privacy-sandbox/protected-audience-api/feature-status/#k-anonymity) the size if specified by `generateBid`) must be k-anonymous for an ad to be shown (this is necessary to ensure the current event-level reporting for interest group win reporting is sufficiently private). For interest groups that have component ads, all of the component ads must also separately meet this threshold for the ad to be shown. Since a single interest group can carry multiple possible ads that it might show, the group will have an opportunity to re-bid another one of its ads to act as a "fallback ad" any time its most-preferred choice is below threshold. This means that a small, specialized ad that is still below the k-anonymity threshold could still choose to participate in auctions, and its interest group has a way to fall back to a more generic ad until the more specialized one has a large enough audience. Interest groups are subject to limits needed to bound resource utilization on the user's device. The browser limits the byte size of interest groups in order to safeguard browser storage used to hold the interest groups. Each interest group is individually limited to 1MB, and calls to `navigator.joinAdInterestGroup` will return an error when called with an interest group that exceeds this limit. To safeguard browser compute resources, the most effective strategy is for sellers to set `perBuyerCumulativeTimeouts` on the auction config. As an added measure, the browser also limits the number of interest groups to which a user may be joined. @@ -293,6 +314,8 @@ const myAuctionConfig = { 'trustedScoringSignalsURL': ..., 'interestGroupBuyers': ['https://www.example-dsp.com', 'https://buyer2.com', ...], 'auctionSignals': {...}, + 'requestedSize': {width: '100', height: '200'}, + 'allSlotsRequestedSizes': [{width: '100', height: '200'}, {width: '200', height: '300'}, ...], 'directFromSellerSignals': 'https://www.example-ssp.com/...', 'sellerSignals': {...}, 'sellerTimeout': 100, @@ -322,6 +345,10 @@ const myAuctionConfig = { 'https://www.another-buyer.com': 345, '*': 456, ...}, + 'perBuyerCurrencies': {'https://example.co.uk': 'GBP', + 'https://example.fr': 'EUR', + '*': 'USD'}, + 'sellerCurrency:' : 'CAD', 'componentAuctions': [ {'seller': 'https://www.some-other-ssp.com', 'decisionLogicURL': ..., @@ -344,6 +371,10 @@ else This will cause the browser to execute the appropriate bidding and auction logic inside a collection of dedicated worklets associated with the buyer and seller domains. The `auctionSignals`, `sellerSignals`, and `perBuyerSignals` values will be passed as arguments to the appropriate functions that run inside those worklets — the `auctionSignals` are made available to everyone, while the other signals are given only to one party. +The optional `requestedSize` field recommends a frame size for the auction, which will be available to bidders in browser signals. This size should be specified in the same format as the sizes in the `adSizes` field of `joinAdInterestGroup`. For convenience, the returned fenced frame config will automatically populate a ``'s `width` and `height` attributes with the `requestedSize` when loaded, though the element's size attributes can still be modified if you want to change the element's container size. Bidders inside the auction may pick a different content size for the ad, and that resulting size will be visually scaled to fit inside the element's container size. + +`allSlotsRequestedSizes` may optionally be used to specify the size of all ad slots on the page, to be passed to each interest group's `trustedBuyerSignalsURL`, for interest groups that request it. All sizes in the list must be distinct. + The optional `directFromSellerSignals` field can also be used to pass signals to the auction, similar to `sellerSignals`, `perBuyerSignals`, and `auctionSignals`. The difference is that `directFromSellerSignals` are trusted to come from the seller because the content loads from a [subresource bundle](https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md) loaded from a seller's origin, ensuring the authenticity and integrity of the signals. For more details, see [2.5 directFromSellerSignals](#25-additional-trusted-signals-directfromsellersignals). In some cases, multiple SSPs may want to participate in an auction, with the winners of separate auctions being passed up to another auction, run by another SSP. To facilitate these "component auctions", `componentAuctions` can optionally contain additional auction configurations for each seller's "component auction". The winning bid of each of these "component auctions" will be passed to the "top-level" auction. How bids are scored in this case is further described in [2.4 Scoring Bids in Component Auctions](#24-scoring-bids-in-component-auctions). The `AuctionConfig` of component auctions may not have their own `componentAuctions`. When `componentAuctions` is non-empty, `interestGroupBuyers` must be empty. That is, for any particular Protected Audience auction, either there is a single seller and no component auctions, or else all bids come from component auctions and the top-level auction can only choose among the component auctions' winners. @@ -362,6 +393,8 @@ Optionally, `sellerExperimentGroupId` can be specified by the seller to support Optionally, `perBuyerPrioritySignals` is an object mapping string keys to Javascript numbers that can be used to dynamically compute interest group priorities before `perBuyerGroupLimits` are applied. See [Filtering and Prioritizing Interest Groups](#35-filtering-and-prioritizing-interest-groups) for more information. +Optionally, `perBuyerCurrencies` and `sellerCurrency` are used for [currency-checking](#36-currency-checking). `sellerCurrency` also affects how [currencies behave in reporting](#53-currencies-in-reporting). + Optionally, `resolveToConfig` is a boolean directing the promise returned from `runAdAuction()` to resolve to a `FencedFrameConfig` if true, for use in a ``, or if false to an opaque `urn:uuid` URL, for use in an ` +``` + +If script that invokes `runAdAuction()` is part of the response to that iframe navigation, `directFromSellerSignalsHeaderAdSlot` can be specified directly without a Promise because `runAdAuction()` cannot be called until after the response - with its DirectFromSellerSignals headers - has been received. + +The browser will make the request for either the `fetch()` or the `iframe` navigation that it otherwise would, with the exception that the request will also include a request header, `Sec-Ad-Auction-Fetch: ?1`. This header indicates to the server that any `Ad-Auction-Signals` response header from the server will only be loaded in auctions via `directFromSellerSignalsHeaderAdSlot` (this is analogous to the guarantees of `Ad-Auction-Only` and `Sec-Fetch-Dest: webbundle` from the [subresource bundle version](#251-using-subresource-bundles) -- scripts on the page cannot set the `Sec-Ad-Auction-Fetch: ?1` request header without using the `{adAuctionHeaders: true}` option). The value of the `Ad-Auction-Signals` header must be JSON formatted, with the following schema: @@ -521,11 +570,11 @@ Ad-Auction-Signals=[{ ] ``` -When invoking `navigator.runAdAuction()`, `directFromSellerSignalsHeaderAdSlot` is used to lookup the signals intended for that auction. `directFromSellerSignalsHeaderAdSlot` is a string that should match the `adSlot` value contained in some `Ad-Auction-Signals` response served from the origin of that auction's seller. Note that for multi-seller or component auctions, each component auction / top-level can specify its own `directFromSellerSignalsHeaderAdSlot`, and the response should be served from that component / top-level auction's seller's origin. Different sellers may safely use the same `adSlot` names without conflict. If `directFromSellerSignalsHeaderAdSlot` matches multiple `adSlot`s from header responses, one of those `adSlot` responses will be chosen arbitrarily. +When invoking `navigator.runAdAuction()`, `directFromSellerSignalsHeaderAdSlot` is used to lookup the signals intended for that auction. `directFromSellerSignalsHeaderAdSlot` is a string that should match the `adSlot` value contained in some `Ad-Auction-Signals` response served from the origin of that auction's seller. Note that for multi-seller or component auctions, each component auction / top-level can specify its own `directFromSellerSignalsHeaderAdSlot`, and the response should be served from that component / top-level auction's seller's origin. Different sellers may safely use the same `adSlot` names without conflict. If `directFromSellerSignalsHeaderAdSlot` matches multiple `adSlot`s from header responses, signals from the most recently-received response will be sent to worklet functions. Furthermore, if a response is received for an adSlot whose name matches that for existing captured signals, memory from the old signals will be released and the new signals will be stored. A response that specifices the same adSlot name in multiple dictionaries is invalid. The JSON will be parsed by the browser, and passed via the same `directFromSellerSignals` worklet functions parameter as in [the subresource bundle](#251-using-subresource-bundles) version of DirectFromSellerSignals, with `sellerSignals` only being delivered to the seller, `perBuyerSignals` only being delivered to the buyer for each buyer origin key, and `auctionSignals` being delivered to all parties. Since the top-level JSON value is an array, multiple `adSlot` responses may be set for a given `Ad-Auction-Signals` header. In the dictionary with the `adSlot`, the `sellerSignals`, `auctionSignals`, and `perBuyerSignals` fields are optional -- they will be passed as null if not specified. -Since both `directFromSellerSignals` and `directFromSellerSignalsHeaderAdSlot` (the parameters on `navigator.runAdAuction()`) set the same `directFromSellerSignals` parameter on the worklet functions, it is not valid to use both `directFromSellerSignals` and `directFromSellerSignalsHeaderAdSlot` in the same auction. However, component auctions in the same top-level auction / the top-level itself do not all need to use the same type of DirectFromSellerSignals (and it's also valid if only some component auctions / the top-level use DirectFromSellerSignals). +Since both `directFromSellerSignals` and `directFromSellerSignalsHeaderAdSlot` (the fields on `navigator.runAdAuction()`) set the same `directFromSellerSignals` parameter on the worklet functions, it is not valid to use both `directFromSellerSignals` and `directFromSellerSignalsHeaderAdSlot` in the same auction. However, component auctions in the same top-level auction / the top-level itself do not all need to use the same type of DirectFromSellerSignals (and it's also valid if only some component auctions / the top-level use DirectFromSellerSignals). Failure to find a matching `adSlot` results in the fields of the `directFromSellerSignals` object passed to worklet functions being set to null, similar to the [subresource bundle version](#251-using-subresource-bundles). @@ -554,11 +603,11 @@ Buyers have three basic jobs in the on-device ad auction: #### 3.1 Fetching Real-Time Data from a Trusted Server -Buyers may want to make on-device decisions that take into account real-time data (for example, the remaining budget of an ad campaign). This need can be met using the interest group's `trustedBiddingSignalsURL` and `trustedBiddingSignalsKeys` fields. Once a seller initiates an on-device auction on a publisher page, the browser checks each participating interest group for these fields, and makes an uncredentialed (cookieless) HTTP fetch to a URL of the form: +Buyers may want to make on-device decisions that take into account real-time data (for example, the remaining budget of an ad campaign). This need can be met using the interest group's `trustedBiddingSignalsURL`, `trustedBiddingSignalsKeys`, and, optionally, `trustedBiddingSignalsSlotSizeMode` fields. Once a seller initiates an on-device auction on a publisher page, the browser checks each participating interest group for these fields, and makes an uncredentialed (cookieless) HTTP fetch to a URL of the form: - https://www.kv-server.example/getvalues?hostname=publisher.com&keys=key1,key2&interestGroupNames=name1,name2&experimentGroupId=12345 + https://www.kv-server.example/getvalues?hostname=publisher.com&keys=key1,key2&interestGroupNames=name1,name2&experimentGroupId=12345&slotSize=100,200 -The base URL `https://www.kv-server.example/getvalues` comes from the interest group's `trustedBiddingSignalsURL`, the hostname of the top-level webpage where the ad will appear `publisher.com` is provided by the browser, `experimentGroupId` comes from `perBuyerExperimentGroupIds` if provided, `keys` is a list of `trustedBiddingSignalsKeys` strings, and `interestGroupNames` is a list of the names of the interest groups that data is being fetched for. The requests may be coalesced (for efficiency) across any number of interest groups that share a `trustedBiddingSignalsURL` (which means they also share an owner). +The base URL `https://www.kv-server.example/getvalues` comes from the interest group's `trustedBiddingSignalsURL`, the hostname of the top-level webpage where the ad will appear `publisher.com` is provided by the browser, `experimentGroupId` comes from `perBuyerExperimentGroupIds` if provided, `keys` is a list of `trustedBiddingSignalsKeys` strings, and `interestGroupNames` is a list of the names of the interest groups that data is being fetched for. `trustedBiddingSignalsSlotSizeMode` is one of `none` (which is the default), `slot-size`, and `all-slots-requested-sizes`. In the second case, `&slotSize=,` is appended to the URL, where width and height are the normalized width and height from the `requestedSize` of the (component) auction's AuctionConfig. "Normalized" means units are always appended, and trailing zeros are removed, so {width: "62.50sw", height: "10.0"} becomes "62.5sw,10px". In the `all-slots-requested-sizes` case, `&allSlotsRequestedSizes=,,,,...` is appended, where all sizes are taken from the (component) auction's `allSlotsRequestedSizes` value. If the corresponding value is not present in the auction configuration, no value is appended. The requests may be coalesced (for efficiency) across any number of interest groups that share a `trustedBiddingSignalsURL` and `trustedBiddingSignalsSlotSizeMode` (which means they also share an owner). The response from the server should be a JSON object of the form: @@ -631,8 +680,10 @@ generateBid(interestGroup, auctionSignals, perBuyerSignals, return {'ad': adObject, 'adCost': optionalAdCost, 'bid': bidValue, - 'render': renderURL, - 'adComponents': [adComponent1, adComponent2, ...], + 'bidCurrency': 'USD', + 'render': {url: renderURL, width: renderWidth, height: renderHeight}, + 'adComponents': [{url: adComponent1, width: componentWidth1, height: componentHeight1}, + {url: adComponent2, width: componentWidth2, height: componentHeight2}, ...], 'allowComponentAuction': false, 'modelingSignals': 123}; } @@ -653,10 +704,13 @@ The arguments to `generateBid()` are: { 'topWindowHostname': 'www.example-publisher.com', 'seller': 'https://www.example-ssp.com', 'topLevelSeller': 'https://www.another-ssp.com', + 'requestedSize': {width: 100, height: 200}, /* if specified in auction config */ 'joinCount': 3, 'recency': 3600000, 'bidCount': 17, - 'prevWins': [[time1,ad1],[time2,ad2],...], + 'prevWinsMs': [[timeDeltaMs1,ad1],[timeDeltaMs2,ad2],...] /* List of this interest group's previous wins. */ + /* Each element is milliseconds since win and the entry from the interest group's 'ads' list + corresponding to the ad that won though with only the 'renderURL' and 'metadata' fields. */ 'wasmHelper': ... /* a WebAssembly.Module object based on interest group's biddingWasmHelperURL */ 'dataVersion': 1, /* Data-Version value from the trusted bidding signals server's response(s) */ } @@ -671,9 +725,14 @@ The output of `generateBid()` contains the following fields: * ad: (optional) Arbitrary metadata about the ad which this interest group wants to show. The seller uses this information in its auction and decision logic. If not present, it's treated as if the value were null. * adCost: (optional) A numerical value used to pass reporting advertiser click or conversion cost from generateBid to reportWin. The precision of this number is limited to an 8-bit mantissa and 8-bit exponent, with any rounding performed stochastically. -* bid: A numerical bid that will enter the auction. The seller must be in a position to compare bids from different buyers, therefore bids must be in some seller-chosen unit (e.g. "USD per thousand"). If the bid is zero or negative, then this interest group will not participate in the seller's auction at all. With this mechanism, the buyer can implement any advertiser rules for where their ads may or may not appear. While this returned value is expected to be a JavaScript Number, internal calculations dealing with currencies should be done with integer math that more accurately represent powers of ten. -* render: A URL which will be rendered to display the creative if this bid wins the auction. -* adComponents: (optional) A list of up to 20 adComponent strings from the InterestGroup's adComponents field. Each value must match an adComponent renderURL exactly. This field must not be present if the InterestGroup has no adComponent field. It is valid for this field not to be present even when adComponents is present. (See ["Ads Composed of Multiple Pieces"](#34-ads-composed-of-multiple-pieces) below.) +* bid: A numerical bid that will enter the auction. The seller must be in a position to compare bids from different buyers, therefore bids must be in some seller-chosen unit (e.g. "USD per thousand"). If the bid is zero or negative, then this interest group will not participate in the seller's auction at all. With this mechanism, the buyer can implement any advertiser rules for where their ads may or may not appear. While this returned value is expected to be a JavaScript Number, internal calculations dealing with currencies should be done with integer math that more accurately represent powers of ten. +* bidCurrency: (optional) The currency for the bid, used for [currency-checking](#36-currency-checking). +* render: A dictionary describing the creative that should be rendered if this bid wins the auction. This includes: + * url: The creative's URL. + * size: A dictionary containing `width` and `height` fields, describing the creative's size (see the interest group declaration above). When the ad is loaded in a fenced frame, the fenced frame's inner frame (i.e. the size visible to the ad creative) will be frozen to this size, and it will be unable to see changes to the frame size made by the embedder. + + Optionally, if you don't want to hook into interest group size declarations (e.g., if you don't want to use size macros), you can have `render` be just the URL, rather than a dictionary with `url` and `size`. +* adComponents: (optional) A list of up to 20 adComponent strings from the InterestGroup's adComponents field. Each value must match one of `interestGroup`'s `adComponent`'s `renderUrl` and sizes exactly. This field must not be present if `interestGroup` has no `adComponent` field. It is valid for this field not to be present even when `adComponents` is present. (See ["Ads Composed of Multiple Pieces"](#34-ads-composed-of-multiple-pieces) below.) * allowComponentAuction: If this buyer is taking part of a component auction, this value must be present and true, or the bid is ignored. This value is ignored (and may be absent) if the buyer is part of a top-level auction. * modelingSignals: A 0-4095 integer (12-bits) passed to `reportWin()`, with noising, as described in the [noising and bucketing scheme](#521-noised-and-bucketed-signals). Invalid values, such as negative, infinite, and NaN values, will be ignored and not passed. Only the lowest 12 bits will be passed. @@ -761,6 +820,16 @@ The `BidFor240Minutes` interest group will have a positive priority if it was jo The `FilterOnDataFromServer` interest group will result in fetching `https://buyer1.com/bidder_signals?publisher=<...>&interest_groups=FilterOnDataFromServer,<...>`, and then if that result has a `perInterestGroupData.FilterOnDataFromServer.priorityVector` object, then that is used just like the `priorityVector` field from the other two examples, except that it's only used for filtering, not to set the priority (unless the group has a true `enableBiddingSignalsPrioritization` field). A [user defined function](https://github.com/privacysandbox/fledge-docs/blob/main/key_value_service_trust_model.md#support-for-user-defined-functions-udfs) could be used on the Protected Audience Key-Value server to calculate that `priorityVector` value, and hence to decide if `FilterOnDataFromServer`'s `generateBid()` method is invoked or if it's filtered out. +### 3.6 Currency Checking + +If participants in the auction need to deal with multiple currencies, they can optionally take advantage of automated currency checking. All of it operates on currency tags, which are required to contain 3 upper-case ASCII letters. + +If the `generateBid()` method returns a `bidCurrency`, and the `perBuyerCurrencies` for that buyer is specified, their consistency will be checked, and if there is a mismatch, the bid will be dropped. Both the `perBuyerCurrencies` for that buyer and returned `bidCurrency` must be present for checking to take place; if one or both are missing the currency check does not take place and the bid is passed on as-is. The returned `bidCurrency` will be passed to `scoreAd()`'s `browserSignals.bidCurrency`, with unspecified currency rendered as `'???'`. + +Currency checking after `scoreAd()` happens only inside component auctions. If the component seller's `scoreAd()` modifies the bid value, the modified bid's currency will be checked; if not, the passed-through bid from the original buyer's currency will be. In either case, the currency will be checked both against the component auction's `sellerCurrency` and top-level auction's `perBuyerCurrencies` as applied to the component auction's seller. As before, both the bid currency and the configured currency in question must be specified for the checking to take place; if one or both are missing that particular currency check does not take place. If there is a mismatch, the bid will not take part in the top-level auction. + +`sellerCurrency` also has an extensive effect on how reporting behaves. Please see the section on [Currencies in Reporting](#53-currencies-in-reporting) for more details. + ### 4. Browsers Render the Winning Ad The winning ad will be rendered in a [Fenced Frame](https://github.com/shivanigithub/fenced-frame): a mechanism under development for rendering a document in an embedded context which is unable to communicate with the surrounding page. This communication blockage is necessary to meet the privacy goal that sites cannot learn about their visitors' ad interests. (Note that the microtargeting prevention threshold alone is not enough to address this threat: the threshold prevents ads which could identify a single person, but it allows ads which identify a group of people that share a single interest.) @@ -811,19 +880,22 @@ The arguments to this function are: 'componentSeller': 'https://www.some-other-ssp.com', 'interestGroupOwner': 'https://www.example-dsp.com/', 'renderURL': 'https://cdn.com/url-of-winning-creative.wbn', - 'bid:' bidValue, + 'bid': bidValue, + 'bidCurrency': 'USD', 'desirability': desirabilityScoreForWinningAd, 'topLevelSellerSignals': outputOfTopLevelSellersReportResult, 'dataVersion': versionFromKeyValueResponse, 'modifiedBid': modifiedBidValue, - 'highestScoringOtherBid': highestScoringOtherBidValue + 'highestScoringOtherBid': highestScoringOtherBidValue, + 'highestScoringOtherBidCurrency': 'EUR' } ``` + * `bidCurrency` and `highestScoringOtherBidCurrency` provide (highly redacted) information on what currency the corresponding numbers are in. Please refer to the section on [Currencies in Reporting](#53-currencies-in-reporting) for more details. * directFromSellerSignals is an object that may contain the following fields: * sellerSignals: Like auctionConfig.sellerSignals, but passed via the [directFromSellerSignals](#25-additional-trusted-signals-directfromsellersignals) mechanism. These are the signals whose subresource URL ends in `?sellerSignals`. * auctionSignals: Like auctionConfig.auctionSignals, but passed via the [directFromSellerSignals](#25-additional-trusted-signals-directfromsellersignals) mechanism. These are the signals whose subresource URL ends in `?auctionSignals`. -The `browserSignals` argument must be handled carefully to avoid tracking. It certainly cannot include anything like the full list of interest groups, which would be too identifiable as a tracking signal. The `renderURL` can be included since it has already passed a k-anonymity check. The browser may limit the precision of the bid and desirability values by stochastically rounding them so that they fit into a floating point number with an 8 bit mantissa and 8 bit exponent to avoid these numbers exfiltrating information from the interest group's `userBiddingSignals`. On the upside, this set of signals can be expanded to include useful additional summary data about the wider range of bids that participated in the auction, e.g. the number of bids. Additionally, the `dataVersion` will only be present if the `Data-Version` header was provided in the response headers from the Trusted Scoring server. +The `browserSignals` argument must be handled carefully to avoid tracking. It certainly cannot include anything like the full list of interest groups, which would be too identifiable as a tracking signal. The `renderURL` can be included since it has passed a k-anonymity check. Because `renderSize` will not be included in the k-anonymity check initially, it is not included in the browser signals. The browser may limit the precision of the bid and desirability values by stochastically rounding them so that they fit into a floating point number with an 8 bit mantissa and 8 bit exponent to avoid these numbers exfiltrating information from the interest group's `userBiddingSignals`. On the upside, this set of signals can be expanded to include useful additional summary data about the wider range of bids that participated in the auction, e.g. the number of bids. Additionally, the `dataVersion` will only be present if the `Data-Version` header was provided in the response headers from the Trusted Scoring server. In the short-term, the `reportResult()` function's reporting happens by calling a `sendReportTo()` API which takes a single string argument representing a URL. The `sendReportTo()` function can be called at most once during a worklet function's execution. The URL is fetched when the frame displaying the ad begins navigating to the ad. Eventually reporting will go through the Private Aggregation API once it has been developed. @@ -848,7 +920,7 @@ The arguments to this function are: * sellerSignals: The output of `reportResult()` above, giving the seller an opportunity to pass information to the buyer. In the case where the winning buyer won a component auction and then went on to win the top-level auction, this is the output of component auction's seller's `reportResult()` method. * browserSignals: Similar to the argument to `reportResult()` above, though without the seller's desirability score, but with additional `adCost`, `seller`, `madeHighestScoringOtherBid` and potentially `interestGroupName` fields: * The `adCost` field contains the value that was returned by `generateBid()`, stochastically rounded to fit into a floating point number with an 8 bit mantissa and 8 bit exponent. This field is only present if `adCost` was returned by `generateBid()`. - * The `interestGroupName` may be included if the tuple of interest group owner, name, bidding script URL and ad creative URL were jointly k-anonymous. + * The `interestGroupName` may be included if the tuple of interest group owner, name, bidding script URL, ad creative URL, and ad creative size (if specified by `generateBid`) were jointly k-anonymous. (Note: until [Q1 2025](https://developer.chrome.com/docs/privacy-sandbox/protected-audience-api/feature-status/#k-anonymity), in the implementation, the ad creative size is excluded from this check.) * The `madeHighestScoringOtherBid` field is true if the interest group owner was the only bidder that made bids with the second highest score. * The `highestScoringOtherBid` and `madeHighestScoringOtherBid` fields are based on the auction the interest group was directly part of. If that was a component auction, they're from the component auction. If that was the top-level auction, then they're from the top-level auction. Component bidders do not get these signals from top-level auctions since it is the auction seller joining the top-level auction, instead of winning component bidders joining the top-level auction directly. * The `dataVersion` field will contain the `Data-Version` from the trusted bidding signals response headers if they were provided by the trusted bidding signals server response and the version was consistent for all keys requested by this interest group, otherwise the field will be absent. @@ -882,7 +954,37 @@ When `joinCount` is passed to `generateBid()`, no noising or bucketing is applie These signals were requested in [issue 435](https://github.com/WICG/turtledove/issues/435). The signals are intented to ship in Chrome M114, they will no longer be available for event level reporting when event level reporting is retired. -#### 5.3 Losing Bidder Reporting +#### 5.3 Currencies in Reporting + +In auctions that involve multiple currencies, there may be values with different units floating around, which makes aggregated information incomprehensible, and event-level information hard to process, potentially requiring participants to interpret currencies of jurisdictions they do no business in. + +To help deal with this scenario, an optional mode is available that converts all bid-related information to seller's preferred currency (in component auctions, reporting for it is for that component's seller). This is configured via the `sellerCurrency` setting in each auction configuration. + +If `sellerCurrency` is set, `scoreAd()` for an auction is responsible for converting bids not already in `sellerCurrency` to `sellerCurrency`, via the `incomingBidInSellerCurrency` field of its return value. A bid already explicitly in the seller's currency cannot be changed by `incomingBidInSellerCurrency` (passing an identical value is a no-op; passing a different one rejects the bid). If neither the original bid is explicitly in `sellerCurrency` nor an `incomingBidInSellerCurrency` is specified, a value of 0 is used as the converted value. + +Note that `incomingBidInSellerCurrency` is different from the modified bid returned by a component auction: it represents a mechanical currency translation of the original buyer's bid, rather than the bid the component auction is making in a top-level auction (which could, perhaps, be reduced by the intermediate seller's fee or the like). It can also be specified in top-level auctions, unlike the modified bid. + +The following table summarizes which APIs get original and which get converted bid values, and how redaction for currency tags works, depending on whether `sellerCurrency` is set or not: +| API | when `sellerCurrency` unset | when `sellerCurrency` set | +| --- | --- | --- | +|`reportWin()` `browserSignals.bid` | Original value | Original value | +|`reportWin()` `browserSignals.bidCurrency` | Currency required by auction configuration, or `'???'` | Currency required by auction configuration, or `'???'` | +|`reportResult()` `browserSignals.bid` | Original value of bid at that auction level (for top-level auction this includes any modification by component auction) | Original value of bid at that auction level (for top-level auction this includes any modification by component auction) (in Chrome since M116) | +|`reportResult()` `browserSignals.bidCurrency` | Currency required by auction configuration, or `'???'` | Currency required by auction configuration, or `'???'` (in Chrome since M116) | +|`reportWin()` `browserSignals.highestScoringOtherBid` | Original value | Converted value | +|`reportWin()` `browserSignals.highestScoringOtherBidCurrency` | `'???'` | `sellerCurrency` | +|`reportResult()` `browserSignals.highestScoringOtherBid` | Original value | Converted value | +|`reportResult()` `browserSignals.highestScoringOtherBidCurrency` | `'???'` | `sellerCurrency` | +| `forDebuggingOnly.report...` keyword `${winningBid}` | Original value | Converted value | +| `forDebuggingOnly.report...` keyword `${winningBidCurrency}` | `'???'` | `sellerCurrency`| +| `forDebuggingOnly.report...` keyword `${highestScoringOtherBid}` | Original value | Converted value | +| `forDebuggingOnly.report...` keyword `${highestScoringOtherBidCurrency}` | `'???'` | `sellerCurrency`| +| `forDebuggingOnly.report...` keyword `${topLevelWinningBid}` | Original value | Converted value (as converted by top-level `scoreAd()`) | +| `forDebuggingOnly.report...` keyword `${topLevelWinningBidCurrency}` | `'???'` | `sellerCurrency` of top-level auction | +| Private Aggregation `winning-bid` | Original value | Converted value | +| Private Aggregation `highest-scoring-other-bid` | Original value | Converted value | + +#### 5.4 Losing Bidder Reporting We also need to provide a mechanism for the _losing_ bidders in the auction to learn aggregate outcomes. Certainly they should be able to count the number of times they bid, and losing ads should also be able to learn (in aggregate) some seller-provided information about e.g. the auction clearing price. Likewise, a reporting mechanism should be available to buyers who attempted to bid with a creative that had not yet reached the k-anonymity threshold. @@ -924,7 +1026,7 @@ const additionalBid = { ] }, - "auctionNonce": "12345678-90ab-cdef-fedcba09876543210", + "auctionNonce": "12345678-90ab-cdef-fedc-ba0987654321", "seller": "https://www.example-ssp.com", "topLevelSeller": "https://www.another-ssp.com" } @@ -938,7 +1040,7 @@ Each additional bid may provide a value for **at most** one of the `negativeInte The `auctionNonce`, `seller`, and `topLevelSeller` fields are used to prevent replay of this additional bid. The `auctionNonce` is described below in section [6.1 Auction Nonce](#61-auction-nonce). The `seller` and `topLevelSeller` fields echo those present in the `browserSignals` argument to `generateBid()` as described in section [3.2 On-Device Bidding](#32-on-device-bidding). In `generateBid()`, these are meant to ensure that the buyer acknowledges and accepts that their bid can participate in an auction with those parties. Additional bids don't have a corresponding call to `generateBid()`, and so the `seller` and `topLevelSeller` fields in an additional bid are intended to allow for the same acknowledgement as those in `browserSignals`. -Additional bids are not provided through the auction config passed to `runAdAuction()`, but rather through the response headers of a Fetch request, as described below in section [6.3 HTTP Response Headers](#63-http-response-headers). However, the auction config still has an `additionalBids` field, which is a Promise with no value, used only to signal to the auction that the additional bids have arrived and are ready to be accepted in the auction. For each additional bid, its owner must be included in interestGroupBuyers for that additional bid to participate in the auction. +Additional bids are not provided through the auction config passed to `runAdAuction()`, but rather through the response headers of a Fetch request or `iframe` navigation, as described below in section [6.3 HTTP Response Headers](#63-http-response-headers). However, the auction config still has an `additionalBids` field, which is a Promise with no value, used only to signal to the auction that the additional bids have arrived and are ready to be accepted in the auction. For each additional bid, its owner must be included in interestGroupBuyers for that additional bid to participate in the auction. ``` navigator.runAdAuction({ @@ -1031,7 +1133,7 @@ To ensure a consistent binary payload is signed, the buyer first needs to string const signedAdditionalBid = { // "bid" is the result of JSON.stringify(additionalBid) "bid": "{\"interestGroup\":{\"name\":\"campaign123\"...},...}" - "signatures": { + "signatures": [ { "key": "9TCI6ZvHsCqMvhGN0+zv67Vx3/l9Z+//mq3hY4atV14=", "signature": "SdEnASmeyDTjEkag+hczHtJ7wGN9f2P2E...==" @@ -1040,7 +1142,7 @@ const signedAdditionalBid = { "key": "eTQOmfYCmLL2gqraPJX6YjryU6hW6yHEwmdsXeNL2qA=", "signature": "kSz0go9iax9KNBuMTLjWoUHQvcxnus8I5...==" }, - } + ] } ``` @@ -1050,20 +1152,34 @@ Note that the key fields are used by the browser both to verify the signature, a The browser ensures, using TLS, the authenticity and integrity of information provided to the auction through calls made directly to an ad tech's servers. This guarantee is not provided for data passed in `runAdAuction()`. To account for this, additional bids use the same HTTP response header interception mechanism that's already in use for the [Bidding & Auction response blob](FLEDGE_browser_bidding_and_auction_API.md#step-3-get-response-blobs-to-browser) and `directFromSellerSignals`. -To use HTTP response headers to convey the additional bids, the request to fetch them will first need to specify the `adAuctionHeaders` fetch flag. +Servers return additional bids to the browser using the `Ad-Auction-Additional-Bid` response header of some `fetch()` request or `iframe` navigation, together with the `additionalBids` field on `navigator.runAdAuction()`. This uses the same syntax as that used to convey `directFromSellerSignals` [using response headers](#252-using-response-headers). +To request additional bids using a [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) call made by some script on the page (including in a subframe), specify an extra option, `{adAuctionHeaders: true}`: + +```javascript +let fetchResponse = await fetch("https://...", {adAuctionHeaders: true}); ``` -fetch("https://...", {adAuctionHeaders: true}); + +The script must resolve the `additionalBids` Promise only after the response for this call has been received. If the script chooses to call `runAdAuction()` after this response is received, the `additionalBids` Promise may be immediately resolved. + +To request additional bids using an [`iframe`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) navigation, specify the `adAuctionHeaders` attribute on the `iframe` element: + +```html + ``` -This signals to the browser that it should look for one or more additional bids encoded as HTTP response headers from this Fetch. Each instance of the `Ad-Auction-Additional-Bid` response header will correspond to a single additional bid. The response may include more than one additional bid by specifying multiple instances of the `Ad-Auction-Additional-Bid` response header. The structure of each instance of the `Ad-Auction-Additional-Bid` header must be as follows: +If script that invokes `runAdAuction()` is part of the response to that iframe navigation, the `adAuctionHeaders` Promise may be immediately resolved from within the iframe because `runAdAuction()` cannot be called until after the response - with its additional bid headers - has been received. + +The browser will make the request for either the Fetch or the `iframe` navigation that it otherwise would, with the exception that the request will also include a request header, `Sec-Ad-Auction-Fetch: ?1`. This header indicates to the server that each `Ad-Auction-Additional-Bid` response header from the server will be decoded as an additional bid and loaded into the auction. Each instance of the `Ad-Auction-Additional-Bid` response header will correspond to a single additional bid. The response may include more than one additional bid by specifying multiple instances of the `Ad-Auction-Additional-Bid` response header. The structure of each instance of the `Ad-Auction-Additional-Bid` header must be as follows: ``` Ad-Auction-Additional-Bid: : ``` -These HTTP response headers are intercepted by the browser and diverted to participate in the auction without passing through the JavaScript context. When all of the additional bids for an auction have been received this way, the seller should resolve the `additionalBids` Promise passed into the auctionConfig that was described in section [6. Additional Bids](#6-additional-bids). The browser will use this as the signal that it's ready to accept the bids provided by the `Ad-Auction-Additional-Bid` response headers into the auction. +The browser uses the auction nonce prefix from each response header to associate each additional bid to its corresponding auction. For single-seller auctions, this maps to a particular call to `runAdAuction()`, whereas for multi-seller auctions, this maps to a particular component auction. + +All `Ad-Auction-Additional-Bid` response headers are intercepted by the browser and diverted to participate in the auction without passing through the JavaScript context. When all of the additional bids for an auction have been received this way, the seller should resolve the `additionalBids` Promise passed as described above. The browser will use this as the signal that it has all of the additional bids intended for this auction. #### 6.4 Reporting Additional Bid Wins diff --git a/FLEDGE_Key_Value_Server_API.md b/FLEDGE_Key_Value_Server_API.md index 1f42cb686..2b16cd971 100644 --- a/FLEDGE_Key_Value_Server_API.md +++ b/FLEDGE_Key_Value_Server_API.md @@ -39,15 +39,10 @@ provides more context about these namespaces. The server can be configured to run in slightly different modes depending on whether it is serving the DSP use case or the SSP use case. -### Subkey +### Hostname -For a given key, a subkey may be used to further specify a dedicated value -override. - -During the query, the browser sets the hostname as the subkey value. When a -query to a particular subkey does not match any existing entry, the server -system can automatically fallback to a default value for the key, specified by -not setting the subkey during data updates. +During the query, the browser sets the hostname. This matches the hostname +described in the main explainer. ## Query API Version 1 @@ -57,16 +52,15 @@ This is the mechanism for the browser client to fetch real-time bidding signals. The API is called during the ad auction process, as described in the [FLEDGE explainer](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#31-fetching-real-time-data-from-a-trusted-server). -The returned values are purely dependent on the keys (namespace + key + subkey), +The returned values are purely dependent on the keys (namespace + key + hostname), except for advanced use cases explicitly agreed upon between browsers and ad tech platforms. A potential advanced use case being discussed is how to provide country-level IPGeo information to the bidders. The API provides read-only access to the key/value data. -As mentioned in the Mutating API section below, possible data staleness may -occur. Different values may be returned for the same keys if reads happen during -data updates, due to the distributed nature of the system. But if the data is -stable, requests are deterministic. +Possible data staleness may occur. Different values may be returned for the +same keys if reads happen during data updates, due to the distributed nature +of the system. But if the data is stable, requests are deterministic. ### Form @@ -75,7 +69,7 @@ GET `https://www.kv-server.example/v1/getvalues` ### Examples ``` -https://www.dsp-kv-server.example/v1/getvalues?subkey=publisher.com&keys=key1,key2 +https://www.dsp-kv-server.example/v1/getvalues?hostname=publisher.com&keys=key1,key2&interestGroupNames=name1,name2 https://www.ssp-kv-server.example/v1/getvalues?renderUrls=url1,url2&adComponentRenderUrls=url3,url4 ``` @@ -137,11 +131,9 @@ https://www.ssp-kv-server.example/v1/getvalues?renderUrls=url1,url2&adComponentR - subkey + hostname The browser sets the hostname of the publisher page to be the value. -

-If no specific value is available in the system for this subkey, a default value will be returned. The default value corresponds to the key when the subkey is not set. DSP @@ -365,13 +357,13 @@ If the restrictions are not followed by the client, for example due to misconfig "type": "object", "additionalProperties": false, "properties": { - "context": { - "description": "global context shared by all partitions", + "metadata": { + "description": "global metadata shared by all partitions", "type": "object", "additionalProperties": false, "properties": { - "subkey": { - "description": "Auxiliary key. For Chrome, it is the hostname of the top-level frame calling runAdAuction(). Set if sent to the trusted bidding signals server.", + "hostname": { + "description": "The hostname of the top-level frame calling runAdAuction().", "type": "string" } } @@ -389,26 +381,27 @@ If the restrictions are not followed by the client, for example due to misconfig "description": "Unique id of the partition in this request", "type": "number" }, - "compressionGroup": { + "compressionGroupId": { "description": "Unique id of a compression group in this request. Only partitions belonging to the same compression group will be compressed together in the response", "type": "number" }, - "keyGroups": { + "arguments": { "type": "array", "items": { - "description": "All keys from this group share some common attributes", + "description": "One group of keys and common attributes about them", "type": "object", "additionalProperties": false, "properties": { "tags": { - "description": "List of tags describing this key group's attributes", + "description": "List of tags describing this group's attributes", "type": "array", "items": { "type": "string" } }, - "keyList": { + "data": { "type": "array", + "description": "List of keys to get values for", "items": { "type": "string" } @@ -419,14 +412,14 @@ If the restrictions are not followed by the client, for example due to misconfig }, "required": [ "id", - "compressionGroup", - "keyGroups" + "compressionGroupId", + "arguments" ] } } }, "required": [ - "context", + "metadata", "partitions" ] } @@ -436,20 +429,20 @@ Example trusted bidding signals request from Chrome: ```json { - "context": { - "subkey": "example.com" + "metadata": { + "hostname": "example.com" }, "partitions": [ { "id": 0, - "compressionGroup": 0, - "keyGroups": [ + "compressionGroupId": 0, + "arguments": [ { "tags": [ "structured", "groupNames" ], - "keyList": [ + "data": [ "InterestGroup1" ] }, @@ -458,7 +451,7 @@ Example trusted bidding signals request from Chrome: "custom", "keys" ], - "keyList": [ + "data": [ "keyAfromInterestGroup1", "keyBfromInterestGroup1" ] @@ -467,14 +460,14 @@ Example trusted bidding signals request from Chrome: }, { "id": 1, - "compressionGroup": 0, - "keyGroups": [ + "compressionGroupId": 0, + "arguments": [ { "tags": [ "structured", "groupNames" ], - "keyList": [ + "data": [ "InterestGroup2", "InterestGroup3" ] @@ -484,7 +477,7 @@ Example trusted bidding signals request from Chrome: "custom", "keys" ], - "keyList": [ + "data": [ "keyMfromInterestGroup2", "keyNfromInterestGroup3" ] diff --git a/FLEDGE_browser_bidding_and_auction_API.md b/FLEDGE_browser_bidding_and_auction_API.md index a3990ee99..19878a956 100644 --- a/FLEDGE_browser_bidding_and_auction_API.md +++ b/FLEDGE_browser_bidding_and_auction_API.md @@ -51,6 +51,11 @@ fetch('https://www.example-ssp.com/auction', { adAuctionHeaders: true, Note that `adAuctionHeaders` only works with HTTPS requests. +Response blobs can also be retrieved using an `iframe` navigation by specifying the `adAuctionHeaders` attribute on the iframe element. As with the Fetch flag, the `adAuctionHeaders` iframe attribute prepares the browser to look for `Ad-Auction-Result` HTTP response headers: +```html + +``` + For each response blob sent back to the browser, the seller’s server attaches a response header containing the base64url encoded (RFC 4648 section 5) SHA-256 hash of the response blob: ``` diff --git a/FLEDGE_extended_PA_reporting.md b/FLEDGE_extended_PA_reporting.md index feac385c4..13d32a0f8 100644 --- a/FLEDGE_extended_PA_reporting.md +++ b/FLEDGE_extended_PA_reporting.md @@ -69,33 +69,30 @@ with an arbitrary `event_key` within `generateBid`, `scoreAd`, `reportWin`, and ### Example 1: Correlating bidding signals with click information. -We consider the scenario where a buyer wants to learn the click through rate of ads when a user has +We consider the scenario where a buyer wants to learn the click-through rate of ads when a user has been in an interest group for a given time. -The buyer may implement `getImpressionReportBucket()` and `getClickReportBucket()` which map an -interest group and the time the user has spent in that interest group to a 128-bit integer. +To generate the bucket that represent interest group age, the buyer may implement `getImpressionReportBucket()` and `getClickReportBucket()` functions which return buckets that map an interest group and the time the user has spent in that interest group to a 128-bit integer as `BigInt`. The `browserSignals.recency` value inside `generateBid()` specifies the duration in minutes since the user joined the interest group. -The buyer can then do the following during generateBid (when the above information is available) +Once the buckets have been derived, the buyer can call Private Aggregation inside `generateBid()`: ``` function generateBid(interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals, browserSignals) { - … + // … privateAggregation.contributeToHistogramOnEvent(“reserved.win”, { - bucket: getImpressionReportBucket(), + bucket: getImpressionReportBucket(), // 128-bit integer as BigInt value: 1 }); privateAggregation.contributeToHistogramOnEvent("click", { bucket: getClickReportBuckets(), // 128-bit integer as BigInt value: 1 }); +} ``` -The above logic will trigger a report if the generated bid wins (see -[reserved.win](#reporting-bidding-data-for-wins)). And another one, if the user later clicks on the -winning ad (this needs to be triggered by the fenced frame itself, see -[reportPrivateAggregationEvent](#reporting-bidding-data-associated-with-an-event-in-a-frame). When -the buyer receives an aggregated report they can infer what the click-through-rate (CTR) was for -users on different “interest group age” buckets. +The impression report will be sent if the [`reserved.win`](#reporting-bidding-data-for-wins) event is triggered, which is a reserved event for when the bid wins the auction. The click report will be sent if the `click` event is triggered by the [`window.fence.reportEvent("click")`](#reporting-bidding-data-associated-with-an-event-in-a-frame) call originating from the fenced frame of the ad. + +The buyer can then generate the summary report of the impression count and click count to infer the click-through rate of the users in different interest group age buckets. ### Example 2: Getting the average bid gap for an ad. @@ -204,6 +201,12 @@ Where `signalBucket` and `signalValue` is a dictionary which consists of: * 6: indicates seller rejected bid because “Creative Filtered - Language Exclusions” * 7: indicates seller rejected bid because “Creative Filtered - Category Exclusions” * 8: indicates seller rejected bid because "Creative Filtered - Did Not Meet The K-anonymity Threshold" + * 9: indicates bid produced by `generateBid()` was rejected because it failed a currency check (e.g. the bid returned by `generateBid()` doesn't match + what's specified by `perBuyerCurrency`) + * 10: indicates bid passed through or altered by `scoreAd()` was rejected + because it failed a currency check (e.g. the bid returned or passed through + by `scoreAd()` in a component auction doesn't match the `sellerCurrency` of + its auction or the `perBuyerCurrency` required by the top-level auction) * Perhaps other values indicating: * generateBid() hitting timeout * The auction was aborted (i.e. calling endAdAuction()) @@ -294,7 +297,7 @@ const auctionConfig = { The seller is able to measure the following for each buyer, assuming permission is granted via the indicated `sellerCapabilities` for that seller: * `interestGroupCount`: The number of the interest groups which could participate in the auction -(i.e. the number of intererest groups on the machine for this buyer -- note the count *isn't* limited by the auction config's `perBuyerGroupLimits`). This requires the `interest-group-counts` `sellerCapabilities` permission. +(i.e. the number of interest groups on the machine for this buyer -- note the count *isn't* limited by the auction config's `perBuyerGroupLimits`). This requires the `interest-group-counts` `sellerCapabilities` permission. * `bidCount`: The number of valid bids generated by this buyer. This requires the `interest-group-counts` `sellerCapabilities` permission. * `totalGenerateBidLatency`: The sum of execution time for all generateBids() in milliseconds. This requires the `latency-stats` `sellerCapabilities` permission. * `totalSignalsFetchLatency`: The total time spent fetching trusted buyer signals in milliseconds. If the interest group didn't fetch any trusted signals, then 0 milliseconds is reported. This requires the `latency-stats` `sellerCapabilities` permission. diff --git a/FLEDGE_k_anonymity_server.md b/FLEDGE_k_anonymity_server.md index d4cb095e7..855d0ef82 100644 --- a/FLEDGE_k_anonymity_server.md +++ b/FLEDGE_k_anonymity_server.md @@ -239,8 +239,14 @@ this, we're exploring options that include having the client request tokens at a constant rate and discard unused tokens. `Query` is a read-only API, so it doesn't have the same abuse concerns as -`Join`. We won't require Private State Tokens, or a Google Account, for a browser -to call `Query`. +`Join`. While we will not require Private State Tokens, or a Google Account, +for a browser to call `Query`, the Query Server will take certain measures +against set abuse to prevent the privacy of end users from being +compromised. If the Query Server has evidence indicating that a set is +corrupted and is more likely to leak identifying information about members, +it will report the k-anonymity status of the set to be `false` until the +risk to users' privacy has been addressed. + #### Differential privacy of public data diff --git a/Fenced_Frames_Ads_Reporting.md b/Fenced_Frames_Ads_Reporting.md index e0182433e..a9c5dbaa6 100644 --- a/Fenced_Frames_Ads_Reporting.md +++ b/Fenced_Frames_Ads_Reporting.md @@ -15,7 +15,7 @@ From a privacy perspective, it is also important to note that the additional inf # Design -The following summarizes the sequence of events for the buyer and seller. Distinguishing these flows here, since in principle, one should be able to report without the help of the other. +The following summarizes the sequence of events for the buyer and seller. Distinguishing these flows here, since in principle, one should be able to report without the help of the other but with an opt-in from the ad's origin for maintaining web's security principles for origins. ![high level diagram](assets/fenced_frames_reporting.png) @@ -55,6 +55,9 @@ The browser processes the beacon by sending an HTTP POST request, like the exist Note `window.fence` here is a new namespace for APIs that are only available from within a fenced frame. In the interim period when FLEDGE supports rendering the winning ad in an iframe, `window.fence` will also be available in such an iframe. +### Enrollment Requirement +The reporting destination URL registered by `registerAdBeacon` is required to have its [site](https://html.spec.whatwg.org/multipage/browsers.html#obtain-a-site) (scheme, eTLD+1) attested for Protected Audience API, otherwise the beacon is not allowed to be sent to this reporting destination. Please see [the Privacy Sandbox enrollment attestation model](https://github.com/privacysandbox/attestation#the-privacy-sandbox-enrollment-attestation-model). + ### Parameters **Event type and data:** Includes the event type and data associated with an event. When an event type e.g. click matches to the event type registered in registerAdBeacon, the data will be used by the browser as the request body in the request sent to the registered URL. @@ -113,6 +116,9 @@ This API is available in the same contexts as `reportEvent` to a preregistered d Unlike `reportEvent` to a preregistered destination, here the browser processes the beacon by sending an HTTP GET request, as per feedback here: https://github.com/WICG/turtledove/issues/477#issuecomment-1524158476. +### Enrollment Requirement +The reporting destination URL specified in `reportEvent`'s `destinationURL` field is required to have its [site](https://html.spec.whatwg.org/multipage/browsers.html#obtain-a-site) (scheme, eTLD+1) attested for Protected Audience API, otherwise the beacon is not allowed to be sent to this reporting destination. Please see [the Privacy Sandbox enrollment attestation model](https://github.com/privacysandbox/attestation#the-privacy-sandbox-enrollment-attestation-model). + ### Parameters **Destination URL:** Includes the desired destination URL for the report, with macros to be substituted based on the buyer worklet's specified values. @@ -125,6 +131,8 @@ window.fence.reportEvent({ 'destinationURL': 'https://adtech.example/impression?cid=555&pub_id=${PUBLISHER_ID}&site=${SOURCE_URL_ENC}&t=123'}); ``` +In this example, the reporting destination eTLD+1 is "adtech.example". [The Privacy Sandbox enrollment attestation model](https://github.com/privacysandbox/attestation#the-privacy-sandbox-enrollment-attestation-model) requires its [site](https://html.spec.whatwg.org/multipage/browsers.html#obtain-a-site) (scheme, eTLD+1) `"https://adtech.example"` to be enrolled as defined in [site-based enrollment](https://developer.chrome.com/blog/announce-enrollment-privacy-sandbox/#site-based-enrollment). Otherwise the beacon will not be sent. + ## registerAdBeacon A similar API was initially discussed here: https://github.com/WICG/turtledove/issues/99 for reporting clicks. The idea is that the buyer and seller side worklets are able to register a URL with the browser in their reportWin and reportResult APIs. A beacon will be sent to the registered URL when events are reported by the fenced frame via reportEvent. @@ -145,6 +153,8 @@ registerAdBeacon({ }); ``` +In this example, the reporting destination eTLD+1 is "adtech.example". [The Privacy Sandbox enrollment attestation model](https://github.com/privacysandbox/attestation#the-privacy-sandbox-enrollment-attestation-model) requires its [site](https://html.spec.whatwg.org/multipage/browsers.html#obtain-a-site) (scheme, eTLD+1) `"https://adtech.example"` to be enrolled as defined in [site-based enrollment](https://developer.chrome.com/blog/announce-enrollment-privacy-sandbox/#site-based-enrollment). Otherwise the beacon will not be sent when there is a `click` event. + ## registerAdMacro Bidder worklets are able to register macros with the browser in their `reportWin()` function. The registered macro values are used to substitute macros in the destination URL of the `reportEvent()` API's parameter. @@ -154,6 +164,8 @@ Two strings. **macro value** The value of the macro that is used to substitute the macro (e.g., ${PUBLISHER_ID}) in `reportEvent()` API’s destination URL parameter. +These strings should be URL-encoded (percent encoded). If either of these strings contains characters that are impossible in URL-encoded strings (i.e., any characters besides the unreserved characters [here](https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_in_a_URI) and `%`), the `registerAdMacro` call will fail with a type error. This is to prevent substituted macros from escaping URL parameters in the destination URL template, e.g. substituting `https://ad.com?param=${PARAM}` with (`PARAM`, `innocuous_value?malicious_param=malicious_value`). + ### Example ``` registerAdMacro(‘PUBLISHER_ID’, ‘123a’); @@ -169,51 +181,53 @@ registerAdMacro(‘SOURCE_URL_ENC’, ‘http%3A%2F%2Fpub%2Eexample%2Fpage’); ### registerAdBeacon -The `reportResult` and `reportWin` worklet code will be able to register an event called `reserved.top_navigation` via `registerAdBeacon`. +The `reportResult` and `reportWin` worklet code will be able to register two new events, called `reserved.top_navigation_start` and `reserved.top_navigation_commit`, via `registerAdBeacon`. ``` registerAdBeacon({ - 'reserved.top_navigation': 'https://adtech.example/click?buyer_event_id=123', + 'reserved.top_navigation_start': 'https://adtech.example/click?buyer_event_id=123', + 'reserved.top_navigation_commit': 'https://adtech.example/click?buyer_event_id=123', }); ``` -The new event, if registered, implies that an automatic beacon will be sent by the browser to the registered URL when a top-level navigation is invoked from within the fenced frame and the navigation was preceded by a call to [window.fence.setReportEventDataForAutomaticBeacons](#api-to-populate-event-data-for-reservedtop_navigation). This will impact top-level navigation initiated from the fenced frame in the same tab (via [unfencedTop target](https://github.com/WICG/fenced-frame/blob/master/explainer/integration_with_web_platform.md#top-level-navigation)) or in a different tab. Note that this beacon is gated on a transient user activation. More details about the beacon are below. - +The new events, if registered, implies that an automatic beacon will be sent by the browser to the registered URL when a top-level navigation is invoked from within the fenced frame and the navigation was preceded by a call to [window.fence.setReportEventDataForAutomaticBeacons](#api-to-populate-event-data-for-reservedtop_navigation). More specifically, a `reserved.top_navigation_start` beacon will be sent when a top-level navigation [begins](https://html.spec.whatwg.org/multipage/browsing-the-web.html#beginning-navigation) and a `reserved.top_navigation_commit` beacon will be sent when the navigation successfully [completes](https://html.spec.whatwg.org/multipage/browsing-the-web.html#ending-navigation). This will impact top-level navigation initiated from the fenced frame in the same tab (via [unfencedTop target](https://github.com/WICG/fenced-frame/blob/master/explainer/integration_with_web_platform.md#top-level-navigation)) or in a different tab. Note that this beacon is gated on a transient user activation. More details about the beacon are below. ### reportEvent -The beacons that are generated from a `reportEvent` invocation or via the automatic `reserved.top_navigation` event will now be automatically eligible for attribution, i.e. the browser appends the `Attribution-Reporting-Eligible` HTTP request header. The beacon responses can then register attribution sources as usual, as described [here](https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#registering-attribution-sources). +The beacons that are generated from a `reportEvent` invocation or via an automatic beacon will now be automatically eligible for attribution, i.e. the browser appends the `Attribution-Reporting-Eligible` HTTP request header. The beacon responses can then register attribution sources as usual, as described [here](https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#registering-attribution-sources). #### Redirects -As mentioned in the explainer above, `reportEvent` beacons are POST requests and carry `eventData` in the request's body. The same will be true for automatic `reserved.top_navigation` requests. Note that for any server redirects of the initial request, the browser sends a GET request and does not include the initial request's body. For attribution registration flow, if the `eventData` needs to be used as part of the redirected request, it must be explicitly passed on as part of the redirect URL. +As mentioned in the explainer above, `reportEvent` beacons are POST requests and carry `eventData` in the request's body. The same will be true for automatic beacon requests. Note that for any server redirects of the initial request, the browser sends a GET request and does not include the initial request's body. For attribution registration flow, if the `eventData` needs to be used as part of the redirected request, it must be explicitly passed on as part of the redirect URL. -### API to populate event data for reserved.top_navigation +##### Enrollment Requirement -Since the `reserved.top_navigation` beacons are automatically generated by the browser, there needs to be some way for those beacons to be associated with a destination and include event data, as it happens in `reportEvent` generated beacons. To achieve this, a new `setReportEventDataForAutomaticBeacons` API can be invoked from within the fenced frame: +For redirects, the redirect URL is not checked for enrollment and attestation. This is because the browser does not add any data directly to the redirect URL. Only the initial reporting destination is checked for attestation for Protected Audience API. The initial reporting destination is responsible for acting in accordance with its attestation if it decides to share any data via the redirect. +### API to populate event data for automatic beacons + +Since automatic beacons are automatically generated by the browser, there needs to be some way for those beacons to be associated with a destination and include event data, as it happens in `reportEvent` generated beacons. To achieve this, a new `setReportEventDataForAutomaticBeacons` API can be invoked from within the fenced frame: ``` window.fence.setReportEventDataForAutomaticBeacons({ - 'eventType': 'reserved.top_navigation', + 'eventType': 'reserved.top_navigation_commit', 'eventData': 'an example string', 'destination': ['seller', 'buyer'], }); ``` +If `setReportEventDataForAutomaticBeacons` is invoked, the browser will send an automatic beacon to all URLs registered via registerAdBeacon for the given event, but it will only send an event data body (the information in eventData) with the HTTP request to destinations specified in the destination field. This means that invoking setReportEventDataForAutomaticBeacons acts as an opt-in by the fenced frame document to allow sending the beacon to all registered URLs, aligning with cross-origin security principles. -Currently, the only `eventType` that `setReportEventDataForAutomaticBeacons` allows is `'reserved.top_navigation'`. Note that the script invoking this API can volunteer this information to a given destination type or not, similar to `reportEvent`, using the `destination` field. +If `setReportEventDataForAutomaticBeacons` is not invoked, the browser will not send an automatic beacon to any registered URLs. -If invoked multiple times, the latest invocation before the top-level navigation would be the one that’s honored. - -`eventData` is optional, and can be empty. If `eventData` is not specified, or is empty, the automatic beacon will still be sent but without an event data body in the HTTP request. +Currently, the only `eventType`s that `setReportEventDataForAutomaticBeacons` allows are `'reserved.top_navigation_start'` and `'reserved.top_navigation_commit'`. Note that the script invoking this API can volunteer this information to a given destination type or not, similar to `reportEvent`, using the `destination` field. -If `setReportEventDataForAutomaticBeacons` is not invoked, the browser will not send an automatic beacon because the `destination` is unknown. +If invoked multiple times, the latest invocation before the top-level navigation would be the one that’s honored. -An automatic beacon can be manually cleared out by calling `setReportEventDataForAutomaticBeacons` with an empty destination list. +Automatic beacon data can be manually cleared out by calling `setReportEventDataForAutomaticBeacons` with an empty destination list. ``` window.fence.setReportEventDataForAutomaticBeacons({ - 'eventType': 'reserved.top_navigation', + 'eventType': 'reserved.top_navigation_start', 'destination': [], }); ``` @@ -225,7 +239,7 @@ window.fence.setReportEventDataForAutomaticBeacons({ function addBeaconData(element) { const data = element.id + " was clicked."; let beacon_event = { - eventType: "reserved.top_navigation", + eventType: "reserved.top_navigation_commit", eventData: data, destination: ["buyer"], } @@ -237,19 +251,24 @@ function addBeaconData(element) { The beacon data will be in place by the time that the navigation starts. When the navigation commits, the automatic beacon will be sent out with event data set to "link1 was clicked.". -The dictionary passed into `setReportEventDataForAutomaticBeacons` also takes an optional `once` boolean that defaults to false. If `once` is set to true, the automatic beacon will only be sent for the next `reserved.top_navigation` event. Beacons will not be sent for subsequent `reserved.top_navigation` events until `setReportEventDataForAutomaticBeacons` is invoked again. When used with a click handler, this can be used to send beacons only for specific top-level navigations, rather than for every top-level navigation. +The dictionary passed into `setReportEventDataForAutomaticBeacons` also takes an optional `once` boolean that defaults to false. If `once` is set to true, the automatic beacon will only be sent for the next event. Beacons will not be sent for subsequent events until `setReportEventDataForAutomaticBeacons` is invoked again. When used with a click handler, this can be used to send beacon data only for specific top-level navigations, rather than for every top-level navigation. -For example, if a frame has multiple links that can perform top-level navigations, but only one of the links should have an automatic beacon associated with it, `setReportEventDataForAutomaticBeacons()` can be called in that link's click handler with `once` set to true. This will ensure that, if another link is clicked after the link with the associated automatic beacon, that other link will not result in a beacon being sent out. +For example, if a frame has multiple links that can perform top-level navigations, but only one of the links is of interest for analytics purposes, `setReportEventDataForAutomaticBeacons()` can be called in that link's click handler with `once` set to true. This will ensure that, if another link is clicked after the link with the associated automatic beacon, that other link will not result in an automatic beacon being sent out. ``` window.fence.setReportEventDataForAutomaticBeacons({ - 'eventType': 'reserved.top_navigation', + 'eventType': 'reserved.top_navigation_start', 'eventData': 'an example string', 'destination': ['seller', 'buyer'], 'once': true, }); ``` +When 3rd party cookies are enabled, automatic beacon requests only (not beacons sent manually through `reportEvent`) allow credentials (cookies) to be set in headers. This was requested by https://github.com/WICG/turtledove/issues/866 in order to help with migration and ARA debugging. These requests are subject to CORS and only occur after opt-in by virtue of calling the `setReportEventDataForAutomaticBeacons` API. + +#### Enrollment Requirement +The reporting destination URL registered by `setReportEventDataForAutomaticBeacons` is required to have its [site](https://html.spec.whatwg.org/multipage/browsers.html#obtain-a-site) (scheme, eTLD+1) attested for Protected Audience API, otherwise the automatic beacon is not allowed to be sent to this reporting destination. Please see [the Privacy Sandbox enrollment attestation model](https://github.com/privacysandbox/attestation#the-privacy-sandbox-enrollment-attestation-model). + # Support for Ad Components For ad components [rendered in fenced frames](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#4-browsers-render-the-winning-ad), the support for event-level reporting described below is available in Chrome starting M114. For ad components rendered in iframes, the support will be available in Chrome starting M115. The support works for all combinations of the top-level ad and ad component being rendered in iframes and/or Fenced Frames. @@ -259,13 +278,13 @@ When a rendered ad is composed of [multiple pieces](https://github.com/WICG/turt ## Design ### Event Type and Reporting Destination -For fenced frames rendering the ad components under the top-level ad fenced frame, the `reserved.top_navigation` event type and corresponding reporting destination registered for the top-level fenced frame are reused when beacons are sent from the ad component fenced frames. +For fenced frames rendering the ad components under the top-level ad fenced frame, the automatic beacon event type and corresponding reporting destination registered for the top-level fenced frame are reused when beacons are sent from the ad component fenced frames. -### Restricted to send `reserved.top_navigation` beacons only +### Restricted to send automatic beacons only * Invocation of the `reportEvent` API from an ad component fenced frame is disallowed. -* The only supported beacon to be sent from an ad component fenced frame is the `reserved.top_navigation` automatic beacon. Note this beacon is gated on a user activation (e.g. click). +* The only supported beacons to be sent from an ad component fenced frame are the `reserved.top_navigation_start` and `reserved.top_navigation_commit` automatic beacons. Note these beacons are gated on a user activation (e.g. click). * To ensure that there is no arbitrary data that can be received at the server from the component ad, the `eventData` field via `window.fence.setReportEventDataForAutomaticBeacons`, if specified, will be ignored. This ensures that information from the component ad URL is not revealed in the event report, or else it could lead to the join of two independently k-anonymous URLs (parent and component ad) at the receiving server. -* To send the beacon from a component fenced frame, `window.fence.setReportEventDataForAutomaticBeacons` must be invoked within the ad component fenced frame with `eventType` set to `'reserved.top_navigation'`. The beacon will be sent when there is a user activation (e.g. click) on the ad component fenced frame, which results in a top-level navigation. +* Automatic beacons will be sent from a component fenced frame (with no event data) when there is a user activation (e.g. click) on the ad component fenced frame, which results in a top-level navigation. The ad component must still opt in using `setReportEventDataForAutomaticBeacons` before the beacon can send. ``` window.fence.setReportEventDataForAutomaticBeacons({ diff --git a/PA_Feature_Detecting.md b/PA_Feature_Detecting.md new file mode 100644 index 000000000..78dc213ab --- /dev/null +++ b/PA_Feature_Detecting.md @@ -0,0 +1,70 @@ +# Feature detecting Protected Audience + +As Chrome ships and experiments with various Protected Audience APIs, it can be useful for web developers to detect the presence of individual features +in users’ browsers. This document seeks to list feature detection mechanisms for each shipped or experimented feature. + +## Protected Audience +[Intent to Ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/igFixT5n7Bs/m/ZNrDcQ2dDQAJ) +``` +let pa = typeof navigator.joinAdInterestGroup !== 'undefined'; +``` +## directFromSellerSignals via headers +[Intent to Ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/JpWOdoPi5Wo/m/YyTaUzkxAgAJ) +``` +let dfss = false; +navigator.runAdAuction({get directFromSellerSignalsHeaderAdSlot(){dfss = true;}}).catch((e)=>{}); +``` +## Negative targeting +[Intent to Ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/xzrWfs-BwFk/m/a90JCji_AAAJ) +``` +let nt = typeof navigator.createAuctionNonce !== 'undefined'; +``` +## clearOriginJoinedAdInterestGroups() +[Intent to Ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/IfmYsMCUoHc/m/yCddTUfgBgAJ) +``` +let nt = typeof navigator.clearOriginJoinedAdInterestGroups !== 'undefined'; +``` +## Interest group limit changes +[Intent to Ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/IfmYsMCUoHc/m/yCddTUfgBgAJ) + +These limits are intended to be guardrails against exceptional behavior, and not meant to be reached under normal conditions, so there should not be a +need to find out what the guardrails are. In [the best practices we previously published](https://developer.chrome.com/docs/privacy-sandbox/protected-audience-api/latency/#fewer-interest-groups-bidding), +we encourage buyers to use fewer interest groups. +## kAnonStatus +[Intent to Ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/IfmYsMCUoHc/m/yCddTUfgBgAJ) +``` +reportWin(auctionSignals, perBuyerSignals, sellerSignals, browserSignals, directFromSellerSignals) { + ... + let ka = typeof browserSignals.kAnonStatus !== 'undefined'; +} +``` +## Bidding & Auction Services +[Intent to Experiment](https://groups.google.com/a/chromium.org/g/blink-dev/c/2bwMHd3Yz7I/m/BwMKwPP6GQAJ) (note that this is an +[Origin Trial](https://developer.chrome.com/en/docs/web-platform/origin-trials/) and as such +requires [Origin Trial tokens](https://developer.chrome.com/en/docs/web-platform/origin-trials/#iframe)) +``` +let ba = typeof navigator.getInterestGroupAdAuctionData !== 'undefined'; +``` +## Recency in generateBid() +[Intent to Ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/-bQKNLit6nw/m/vPe0uSXtAAAJ) +``` +generateBid(interestGroup, auctionSignals, perBuyerSignals, + trustedBiddingSignals, browserSignals, directFromSellerSignals) { + ... + let rg = typeof browserSignals.recency !== 'undefined'; +} +``` +## Rounding bids and scores +[Intent to Ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/-bQKNLit6nw/m/vPe0uSXtAAAJ) + +This is a privacy protection that does not require different behavior from Protected Audience API callers and hence has no feature detection mechanism. +## Credentialed automatic beacons +[Intent to Ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/rMyTWCo-f_I) + +Whether automatic beacons will attach credentials cannot be detected from inside Protected Audience ads. From the perspective of the server, the request +has either credentials mode “omit” (feature disabled) or “include” (feature enabled), which affects how headers are processed in the same way as the rest +of the web platform. +## reserved.top_navigation_start/commit +This cannot be detected from the web platform. Instead, an ad auction can register both a `reserved.top_navigation_commit` and a `reserved.top_navigation` +beacon, and then the ad frame can set the same automatic beacon data for both event types. The beacon destination server can then compare +`top_navigation_commit` and `top_navigation` beacons, and filter out duplicate beacons that have the same exact data. diff --git a/Proposed_First_FLEDGE_OT_Details.md b/Proposed_First_FLEDGE_OT_Details.md index 1b584ff3d..b0590a7c7 100644 --- a/Proposed_First_FLEDGE_OT_Details.md +++ b/Proposed_First_FLEDGE_OT_Details.md @@ -61,10 +61,14 @@ As the FLEDGE explainer talks about, the FOT#1 will include event-level reportin The FOT#1 will include event-level reporting for both winning and losing bids. Implementations of the `generateBid()` and `scoreAd()` worklets, provided by the buyers and sellers respectively in the auction, may call a `forDebuggingOnly.reportAdAuctionLoss()` API which takes a single string argument representing a URL. The text placeholders below will be replaced with the corresponding value from the auction when found in the reporting URL's query parameters (note that due to http://crbug.com/1338233, prior to Chrome version 107.0.5286.0, the replacements only took place in the path of the URL) * “${winningBid}” - The value of the winning bid. In component auctions, this value comes from the component auction and not the top-level auction. +* “${winningBidCurrency}” - If the auction has a `sellerCurrency` configured, this will be its currency tag; otherwise it is `'???'` to denote that it's in bidder's original currency. * “${madeWinningBid}” - A boolean value representing whether the owner of this interest group made the winning bid, either via this interest group, or another interest group with the same owner. In component auctions, this value comes from the component auction and not the top-level auction. * “${highestScoringOtherBid}” - The value of the bid that was scored as second highest by the seller’s scoreAd script. Note that this may not be the second highest bid value, since scores and bids may be independent. In component auctions, this value comes from the component auction and not the top-level auction. +* “${highestScoringOtherBidCurrency}” - The currency `highestScoringOtherBid` is in. If the auction has a `sellerCurrency` configured, this will be its currency tag; otherwise it is `'???'` to denote that it's in bidder's original currency. * “${madeHighestScoringOtherBid}” - A boolean value representing whether the owner of this interest group made the ${highestScoringOtherBid} bid, either via this interest group, or another interest group with the same owner. In component auctions, this value comes from the component auction and not the top-level auction. * “${topLevelWinningBid}” - The value of the bid that won the top-level auction. This value is only reported in component auctions. +* “${topLevelWinningBidCurrency}” - The currency `topLevelWinningBid` is in. +If the top-level auction has a `sellerCurrency` configured, this will be its currency tag; otherwise it is `'???'` to denote that it's in whatever currency the component auction made the bid in. * “${topLevelMadeWinningBid}” - A boolean value representing whether the owner of this interest group made the bid that won the top-level auction, either via this interest group, or another interest group with the same owner. This value is only reported in component auctions. If the bid being generated or scored loses the auction, the URL will be fetched. These worklets may also call a `forDebuggingOnly.reportAdAuctionWin()` API which operates similarly to `forDebuggingOnly.reportAdAuctionLoss()` API but only fetches the URL after a winning bid or score. diff --git a/Release_Notes.md b/Release_Notes.md index 7fcd2029e..8592296b7 100644 --- a/Release_Notes.md +++ b/Release_Notes.md @@ -6,6 +6,16 @@ * Functions that are called from Protected Audience worklets are now only accessible from inside the worklets, not from the global scope. See [#489](https://github.com/WICG/turtledove/issues/489) for more information. +## Chrome M114 + +* Support the ability to specify `requestedSize` in the auction config, which is eventually stored in the winning fenced frame config's container size. The `requestedSize` may not be accessible through browser signals in the auction until M116, and is a lower priority because it is a convenience feature only (presumably the size of the ad slot is already passed in through other signals, if it is needed). + + +## Chrome M113 + +* Support some of the size-related API changes (the ability to declare ad sizes in `joinAdInterestGroup`, include sizes with bids in `generateBid`, and have those sizes macro'd into the URL with `AD_WIDTH` and `AD_HEIGHT` macros), in a backwards compatible and opt-in way. + + ## Chrome M109 * Since version 109.0.5414.16, the [`sendReports` parameter to `navigator.deprecatedURNToURL()`](https://github.com/WICG/turtledove/blob/main/Proposed_First_FLEDGE_OT_Details.md#advertisement-rendering) is respected. diff --git a/fledge-tester-list.md b/fledge-tester-list.md index c60b772b0..eeccb5ba7 100644 --- a/fledge-tester-list.md +++ b/fledge-tester-list.md @@ -44,8 +44,8 @@ The usefulness of this page depends on testers sharing information and updates. | --------------- | --------------- | ---------------------- | ------------------------------------- | ------------------ | | Criteo | DSP | Started in 2022, long term commitment | [An update on FLEDGE testing](https://medium.com/criteo-engineering/an-update-on-fledge-chrome-testing-d0046430a3ec)| privacy-sandbox-testing@criteo.com | | Criteo | SSP | November 2023 | | privacy-sandbox-testing@criteo.com | -| Teads | DSP | January 2024 | | privacysandbox@teads.com | -| Teads | SSP | January 2024 | | privacysandbox@teads.com | +| Teads | DSP | March 15th 2024 - May 15th 2024 | | privacysandbox@teads.com | +| Teads | SSP | March 15th 2024 - May 15th 2024 | | privacysandbox@teads.com | | NextRoll | DSP | 2024-01-30 | coming soon | privacysandbox@nextroll.com | | OpenX | SSP | Limited testing in progress | | joel.meyer@openx.com | | RTB House | DSP | Continuous testing ongoing; long term commitment. | https://blog.rtbhouse.com/whitepaper-deep-insights-from-early-fledge-experiments/ | privacysandbox@rtbhouse.com | @@ -57,6 +57,7 @@ The usefulness of this page depends on testers sharing information and updates. | MicroAd | SSP & DSP | | | privacysandbox@microad.co.jp | | Blendee | SSP & DSP | | | privacysandbox@blendee.com | | Adlook (subsidiary of RTB House) | DSP | Continuous testing ongoing; long term commitment. | | privacysandbox@adlook.com | +| PrimeAudience (subsidiary of RTB House) | Ad Network | Continuous testing ongoing; long term commitment. | | contact@primeaudience.com | | Microsoft (Xandr, MSAN) | SSP + DSP(s) | Testing | | privacy_sandbox@microsoft.com | | Nexxen (Unruly/Tremor/Amobee) | SSP & DSP| 2023-2024 | coming soon | privacysandbox@nexxen.com | | Triplelift | SSP | Jan 2024 | | prod-privacysandbox@triplelift.com | @@ -67,6 +68,10 @@ The usefulness of this page depends on testers sharing information and updates. | Onetag | DSP & SSP | 2023-2024 | | privacysandbox@onetag.com | | Yahoo Inc | DSP | beginning 15 Jan 2024 | | googleprivacysandbox@yahooinc.com | | Magnite | SSP | January 2024 | | privacysandbox@magnite.com | +| Globo | DSP & SSP | Testing | | adtech-delivery@g.globo | +| Mediavine | DSP & SSP | 2023-2024 | | privacysandbox@mediavine.com | +| Jivox | Ad Server (DCO) | Testing in progress | | jvx-google-privacy-sandbox-team@jivox.com | +| Wirtualna Polska Media | Publisher & SSP | Running Protected Audience API tests since July 2023 | https://github.com/grupawp/PAapi | privacysandbox@grupawp.pl | ## Table - Publishers and Advertisers Interested in Testing or Early Adoption Companies who may be interested in participating in tests and early adoption opportunities provided by ad tech companies. @@ -79,4 +84,6 @@ Companies who may be interested in participating in tests and early adoption opp | Clarin | Publisher | | mfranco@clarin.com | | Terra Networks | Publisher | | adtech.terra.br@telefonica.com | | OLX Brasil | Publisher | | adtech@olxbr.com | +| Globo | Publisher | | adtech-delivery@g.globo| +| A Gazeta | Publisher | | cdutra@redegazeta.com.br | diff --git a/meetings/2023-10-25-FLEDGE-call-minutes.md b/meetings/2023-10-25-FLEDGE-call-minutes.md new file mode 100644 index 000000000..f7de0b6a2 --- /dev/null +++ b/meetings/2023-10-25-FLEDGE-call-minutes.md @@ -0,0 +1,137 @@ +# Protected Audience (formerly FLEDGE) WICG Calls: Agenda & Notes + +Calls take place on most Wednesdays, at 11am US Eastern time; check [#88](https://github.com/WICG/turtledove/issues/88) for exceptions. + +That's 8am California = 5pm Paris time = 3pm UTC (during summer). + +This notes doc will be editable during the meeting — if you can only comment, hit reload + +Notes from past calls are all on GitHub [in this directory](https://github.com/WICG/turtledove/tree/main/meetings). + + +# Next video-call meeting: Wednesday Oct 25, 2023 + + +## Attendees: please sign yourself in! + + + +1. Michael Kleber (Google Privacy Sandbox) +2. Brian May (dstillery) +3. Roni Gordon (Index Exchange) +4. Youssef Bourouphael (Google Chrome) +5. David Dabbs (Epsilon) +6. Sven May (Google Privacy Sandbox) +7. Caleb Raitto (Google Chrome) +8. Paul Jensen (Google Chrome) +9. Russ Hamilton (Google Chrome) +10. Orr Bernstein (Google Privacy Sandbox) +11. Manny Isu (Google Chrome) +12. Sid Sahoo (Google Chrome) +13. Don Marti (Raptive) +14. Isaac Schechtman (Criteo/BidSwitch) +15. Joel Meyer (OpenX) +16. Risako Hamano (Yahoo Japan) +17. Laurentiu Badea (OpenX) +18. Matt Menke (Google Chrome) +19. Brian Schmidt (OpenX) +20. Isaac Foster (Microsoft Ads) +21. Stan Belov (Google Ads) +22. Shivani Sharma (Google Chrome) +23. Alonso Velasquez (Google Chrome) +24. Alex Cone (Google Privacy Sandbox) +25. Marco Lugo (NextRoll) +26. Abishai Gray (Google Chrome) +27. Jeroune Rhodes (Google Privacy Sandbox) +28. Leeron Israel (Google Chrome) + + +## Note taker: Manny Isu + + +# Agenda + + +## Process reminder: Join WICG + +If you want to participate in the call, please make sure you join the WICG: https://www.w3.org/community/wicg/ + + +## Suggest agenda items here: + + + +* [MK] Request for feedback, buyside and sellside buy-in for ad slot size idea: https://github.com/WICG/turtledove/issues/869 +* Roni Gordon + * Additional browser event beacons - https://github.com/WICG/turtledove/issues/826 + * K/V lookup timeouts - https://github.com/WICG/turtledove/issues/814 + * Macro substitutions - https://github.com/WICG/turtledove/issues/817 + * API versioning - https://github.com/WICG/turtledove/issues/823 + * Sensitive signals - https://github.com/WICG/turtledove/issues/824 +* Isaac: + * Multi Tag Support via “Mixed Ranking”: (really, this + multi tag + bit leak discussion and how we can be creative) https://github.com/WICG/turtledove/issues/846 + * Buyer/Seller Reporting Questions: https://github.com/WICG/turtledove/issues/682#issuecomment-1710965068 + * Optional decouple bidding/reporting function urls to allow smaller k tuple: https://github.com/WICG/turtledove/issues/679#issuecomment-1703973736 +* [Jeroune] Announcement + * The Google Privacy Sandbox team will be hosting our next set of webinars on Protected Audience. This set will focus on multi-seller auctions, where we will cover sequential auction setup, different types of auctions and participants involved, examine the overview diagram, and walkthrough a detailed sequence diagram along with code. The first **Americas friendly session** is happening on** Nov. 7th 1-2 pm ET**. A second **EMEA friendly session** is happening **Nov. 8th 7-8 am ET**. To join, please register below: + * AMER: [rsvp.withgoogle.com/events/protected-audience-multisellerauction-amer\_a27cb5](https://rsvp.withgoogle.com/events/protected-audience-multisellerauction-amer_a27cb5) + * EMEA: https://rsvp.withgoogle.com/events/protected-audience-multisellerauction-emea\_a27cb5\_339693 + + +# Notes + + +## [MK] Request for feedback, buyside and sellside buy-in for ad slot size idea: https://github.com/WICG/turtledove/issues/869 + + + +* Feature request for Ad slot size to be included in the request to the KV server; more directed at Buyer KV server - Is it possible for the KV server to know what slot size the ads is to make retrieving bidding signals more efficient? +* Generally, the browser sends a single KV request… reusable for all the different PA auctions on a page. In an attempt to square the circle, proposing a new parameter that can be added to the auction config. When kicking off the auction, what if we added a way for the top-level SSPs to say "here are all the different ad slot sizes on the page". Then the KV server request could include all of them and the KV response will take that into consideration; we can have caching and ad slot size known by the KV server. It seems viable if the buyers and sellers like this idea. +* [Brian] What percentage of pages know what the ad slot will be? + * [Isaac] Might need to run some queries to determine the answer. I think it is significant + * [MK] With the proposal, if you later come across an ad auction for a size that is not on the list, you can still run a PA auction on it, it will just trigger an additional KV request + * [Brian] So it seems like we are significantly increasing the number of moving parts in a request? It seems like something we should take into consideration + * [MK] Yes, for sure +* [Roni] **Point #1: **Let's let DSP KV server url opt into this behavior by having some macro in this url - like responsive ads in the render url. So a DSP that doesn't care about slot size doesn't pay any new cost. + * **Point #2: **GPT tag should know all the slot sizes on the page, and it should know how to do this. I think anything we do here would have to do with libraries; I think it should be defined dynamically from javascript to javascript +* [Joel] This is a lot of lift from GAM specific use case. SSPs won’t benefit because we do not get cache. How does viewability factor into this? + * [MK] The question is to determine whether this would be useful to other buyers — does slot size matter to you in SSP KV? Also, I do not think that viewability should come into this much. It seems like predictive model on viewability to take into account the domain is already possible with the KV server. + * [Brian] The call to the KV server cannot be used as an indication that something is viewable? + * [MK] I don’t know what logic SSPs use to kick off the auction but certainly possible the call might happen after the load page or at the beginning. Not sure +* [Isaac] From a DSP perspective, the size of the creative can vary… the creative ID on the trusted bidding signals.. Might help with real time requests for the creatives. On the buyside, this will be relevant to help them return real time signals. Anything that can be done by framework will be cheaper. I would see particular value for a DSP who has that type of creative. Also could be very useful for filtering, not sending back signals for creatives that cannot bid due to slot size mismatch. + + +## Additional browser event beacons - https://github.com/WICG/turtledove/issues/826 + + + +* [Roni] There are two classes of events - At the moment, because JS executed is controlled by buyside, the buyer has to indicate that these reserve event. Without buyer writing, the seller will get none of these events. How do we make it so that frames do not leak things that they aren’t supposed to leak, but having buyers and sellers automatically get the reserved events. + * [MK] I would broaden a little and say there are 3 kinds of events + * **Render:** Did this url get put in an iFrame? That is something that is automatic and everyone gets to find out and does not require any special opt in. You can observe from outside the frame. + * **Viewability:** No automatic way of SSP doing viewability beaconing today. We should invent something like intersection observer, and we should be able to make it happen automatically as configured by SSP, because this is something that can be observed from the outside page. + * **Click Event:** Front the browser POV, it happens inside the ad. Like every other cross domain iFrame, somebody outside cannot tell what happens inside. With some opt-in, the iframe will need to say it is okay for somebody outside to see + * [Roni] The render event is not automatic because there is no reserved event that gets fired automatically. + * MK: the reportWin and reportResult are indirectly indications of render event + * [from zoom chat, Matt Menke]: It's fenced frame nav start, not rendering that triggers reportWin reports. + * MM: I was just referring to the sendReportTo() and debug reports (which aren't based on navigation initiated by the fenced frame, but rather the navigation to the ad URL in the first place) + * [MK] The thing you are asking for is an event for ad click navigation… you want to know that the ads got to send the user to the landing page + * [Roni] Even if all we can get is that someone clicked on the bounding box of the rectangle - knowing that the user interacted with the ad container. + * [Shivani] Knowing that the click happened is more than what you can get today. There is a change in progress where the opt in has been made a little bit more flexible - as long as API is called, anyone who has registered to receive the beacon will receive it. It’s like an opt-in from the buyer side. + * [Joel] Echoing Roni’s request. Publishers are in the dark about what happens on their page. There is a huge need. If Chrome were to standardize a way, this will help move adtech world forward to improve publisher awareness, could end better than today. + * [MK] As an SSP, if we found a way to make it possible, would you go so far to take the position to refuse to render any ads that does not opt in to sharing this click-event information? + * [Joel] I will turn the decision over to publishers - It’s a monetization decision. + * [MK] Roni, do you think that if we did something like that, would that be satisfactory to your issue? + * [Roni] Yes, I would like to see how that evolves. + * [Shivani] For the non JS opt in, should we continue discussions on the GH issue? + * [MK] Yes, we should + * [Paul] We do have security concerns because you learn something happening inside the ads, so we have to get opt-in. And opt-in cannot happen just from the IG… the owner of the IG can provide any render url and we can't just let them find out what is happening inside the ad + * [Roni] Are we saying that there is no way for the frame to know that the buyer are seller origin is involved? + * [Paul] The big issue is that on the web, anyone can create a frame as a parent but there is no way to verify that. We are looking for a way that an ad can say this buyer is okay to share. + * [MK] Roni is right that the browser has the ability to know that the ads appeared, but the point is that even if we added an API to help with this, a naive ad does not use any API to check whether the party that caused it to be rendered was someone they expected + * From zoom chat + * Paul Jensen + * HTTP response headers are an efficient way to do this. /.well-known files are a less efficient but sometimes easier way + * Matt Menke + * HTTP response headers also allow us to let the SSP or DSP demand the header be present, or we won't load the ad. + * If folks want that ability + * [MK] No perfect solutions. We will keep discussing the issue. diff --git a/meetings/2023-11-01-FLEDGE-call-minutes.md b/meetings/2023-11-01-FLEDGE-call-minutes.md new file mode 100644 index 000000000..027720c0f --- /dev/null +++ b/meetings/2023-11-01-FLEDGE-call-minutes.md @@ -0,0 +1,217 @@ +# Protected Audience (formerly FLEDGE) WICG Calls: Agenda & Notes + +Calls take place on most Wednesdays, at 11am US Eastern time; check [#88](https://github.com/WICG/turtledove/issues/88) for exceptions. + +That's 8am California = 4pm Paris time = 3pm UTC + +**NOTE THAT Europe clocks have changed but US clocks have not! So this week's meeting is at an usual hour for Europe folks!** + +This notes doc will be editable during the meeting — if you can only comment, hit reload + +Notes from past calls are all on GitHub [in this directory](https://github.com/WICG/turtledove/tree/main/meetings). + + +# Next video-call meeting: Wednesday Nov 1, 2023 + + +## Attendees: please sign yourself in! + + + +1. Michael Kleber (Google Privacy Sandbox) +2. Brian May (dstillery) +3. Sven May (Google Privacy Sandbox) +4. Paul Jensen (Google Privacy Sandbox) +5. Roni Gordon (Index Exchange) +6. Youssef Bourouphael (Google Privacy Sandbox) +7. Orr Bernstein (Google Privacy Sandbox) +8. Matt Menke (Google Chrome) +9. Laurentiu Badea (OpenX) +10. Harshad Mane ( PubMatic ) +11. David Dabbs (Epsilon) +12. Andrew Kwok (Criteo) +13. Antoine Niek (Optable) +14. Xavier Capaldi (Optable) +15. Alex Cone (Google Privacy Sandbox) +16. Caleb Raitto (Google Chrome) +17. Mahdi Sadjadi (Yieldmo) +18. Leeron Israel (Google Privacy Sandbox) +19. Alex Johnston (Yieldmo) +20. Andrew Pascoe (NextRoll) +21. Marco Lugo (NextRoll) +22. Sid Sahoo (Google Chrome) + + +## Note taker: Orr Bernstein + + +# Agenda + + +## Process reminder: Join WICG + +If you want to participate in the call, please make sure you join the WICG: https://www.w3.org/community/wicg/ + + +## Suggest agenda items here: + + + +* Roni Gordon + * K/V lookup timeouts - https://github.com/WICG/turtledove/issues/814 + * Macro substitutions - https://github.com/WICG/turtledove/issues/817 + * API versioning - https://github.com/WICG/turtledove/issues/823 + * Sensitive signals - https://github.com/WICG/turtledove/issues/824 +* Isaac: + * Multi Tag Support via “Mixed Ranking”: (really, this + multi tag + bit leak discussion and how we can be creative) https://github.com/WICG/turtledove/issues/846 + * Buyer/Seller Reporting Questions: https://github.com/WICG/turtledove/issues/682#issuecomment-1710965068 + * Optional decouple bidding/reporting function urls to allow smaller k tuple: https://github.com/WICG/turtledove/issues/679#issuecomment-1703973736 + + +# Notes + + + +### K/V lookup timeouts - https://github.com/WICG/turtledove/issues/814 + +* Roni Gordon + * Want to understand timeouts + * About how one defines timeouts in an auction config and what they’re governing. + * Where is the KV time considered? It happens in between the function calls but in the same worklet. Paul provided a clarification on the bug. + * Roni is trying to better understand how to configure it. Similar question for the seller KV time. No notion of “cumulative” because there aren’t multiple sellers in a component auction. + * Paul Jensen + * When we started thinking about different ways to do timeouts, we had limits on the number of interest groups and the amount of time each can take in generateBid and scoreAd. Also protects against a potential privacy leak. + * For performance, the more important thing is wall clock time - overall time, not just time for each one. We want to be less opinionated about how long each one takes. So that’s when we moved to the cumulative timeout. Also, the trusted signal call can take quite a while. So, that’s why, as we moved to cumulative, we included the total wall time an interest group takes. + * As for why there isn’t a trusted seller signals timeout, nobody has asked for it. There is a 30 second timeout, but hopefully nobody reaches that. How many times it gets invoked is sort of under the control of the buyers. If we get 10 bids, we could make one trusted seller signals call for all ten bids, or we could make ten requests, depending on how those requests come in. Also, the seller has an overall control for timeout - the abort signal they can pass in. + * Roni, what kind of timeout are you looking for? + * Roni + * KV needs to respond in a certain amount of time, and needs to reflect the potential timeouts. Nothing prevents a slow seller KV call from taking over the auction. + * Paul + * You can use the abort signal to control total time of the auction. The real question is, if we added a seller trusted signal timeout, would you use it? + * Michael Kleber + * Usual process is that the timeout is applied to other parties. Could be that the top-level seller could apply a timeout on the component sellers. + * Roni + * Michael + * There is not a timeout right now that covers the seller KV lookup. If all of these are maximally slow, the whole auction might take 30 seconds plus a little bit. [From chat: actually, could be a minute.] The top level seller would be highly incentivized to not work with component sellers that take so long. + * Roni + * So, incentivized to return the KV as quickly as possible to reduce the likelihood of timing out the auction. If the timeout is for the KV call, then the KV call times out and it’s like the bid isn’t there anymore. If the signal times out, the generateBid call could decide what to do in the absence of that signal. + * If I set just the cumulative timeout, do I still set the interest group timeout? + * Paul + * You probably do want to set both timeouts, but set them to the same value. Per-buyer timeout to the same as the cumulative one. + * Roni + * The spec sets a default value of per-buyer timeout, so not specifying it means you get that default. + * Michael + * Default value of 50 ms, maximum allowable value of 500 ms. + * Roni + * So, I have to specify both and hope their generateBid is fast. + * Is there any indication that this timeout has happened? That I’ve run out of time because I’m too slow? + * Paul + * Per-function timeout, there might be a way. But the cumulative one is trickier, because the bids before the timeout you get, but the bids after the timeout you don’t. + * We do have private aggregation for times - your trusted bidding signals and your generateBid times. Don’t know if we ever added a value to the enum to indicate that generateBid hit its timeout. + * At the beginning of generateBid - while you have forDebugOnly reporting - you can specify at the start of generateBid, you could specify that it reached the end of generateBid, and then to see if these numbers diverged. + * Matt Menke + * With cumulative timeouts, we kill the worklets, so it doesn’t even start generateBid for those. + * Brian May + * These things are implying that there are resource constraints on the browser, and that the browser is taking preemptive action if it hasn’t reached a point where it needs to do something to show an ad. Have to figure out if we’re in a situation where the same couple of interest groups are getting evaluated, and there’s starvation of interest groups lower down. Unlike in a server, where we have pretty tight control over what happens between the time we get a request and the time we show an ad. + * Paul + * Happy medium is that seller specifies a cumulative timeout. Buyer optimizes how they want to do trusted bidding signals, could spend more time there and less in on-device generateBid script or vice versa. + * Brian + * Context that a buyer has control over. No way for a buyer to get a sense of what the situation on a given browser is going to be, how to handle to the competition for resources across buyers. + * Michael + * The seller is the one who controls how these resources are shared using the cumulative timeouts. + * Brian + * But the buyer can’t optimize because they don’t have enough information to see, for this auction, what the situation is. + * Michael + * Buyers can prioritize interest groups; higher priority interest groups go first. If the seller can tell that the device is a slow device, and they want to allocate more time for the auction. + * Brian + * As a seller, I don’t have a good way to optimize calls, and if there are too many interest groups, how to prioritize them. + * Paul + * Buyer should prioritize the interest groups, how to prioritize. The seller should be worrying about who the buyers are, what the resources are, and what the latency acceptable to the publisher is. + * Michael + * Seller won’t know which buyers have an interest group on the device, so they can’t decide how to allocate resources among those buyers that will participate in the auction. Don’t want this to be in JavaScript because that would delay the start of the auction until after we’ve started a JavaScript environment, but can do this in a declarative way. + * Brian + * If there are too few resources on the browser, then if I have more things than I can do in the amount of time, then I might visit the same first 10 interest groups every time, and never see the following 30 interest groups. + * Michael + * Yes, buyers get to prioritize their interest groups. + * Brian + * Will run into a situation soon where interest groups will be starved as a result of this. + * Michael + * Yes, if some buyers are not going to be able to run at all because of cumulative timeouts, do sellers get to prioritize which buyers get to run or is there some randomness there? + * Brian + * On the server side, we give campaigns that tend to be paid attention to less an opportunity to squeeze themselves in by making space. + * Matt (via chat) + * Buyers are run in the order they’re specified in the auction config. We can't create an infinite number of isolated processes, each with its own JS thread. So there's a limit of buyers that we process at once. + * Michael + * If there’s insufficient resources on the browser, + * Roni + * Somewhat of an awkward position. We don’t play favorites with buyers. If I put somebody first, it’s not just that they’re first because I chose to alphabetize my list. I’ll need to randomize the order of buyers, which I didn’t know I need to do. + * David + * Request from the chat - Matt put some interesting comments - could he pull that together in a concise way, in the spec or howto doc. Reading the chat, it’s not too clear. + * Sid Sahoo + * Could Paul explain how the top level seller timeout works with component sellers today? + * David + * One seller timeout, which governs the scoreAd of component sellers. + * Paul + * Matt - does per-buyer cumulative timeout apply to component sellers? + * Matt + * Each auction is mostly run independently so that their fields don’t affect the other auctions. + * Sid + * How does the top-level seller timeout play with the component auctions? Can the top-level seller influence the component auctions? + * Paul + * The auction configs may be passing through the top level seller. So they could add an abort seller in each component seller config if they wanted. + * Roni - is there a feature request here besides improving our documentation? + * Roni + * Curious about how timeouts for component and top-level sellers. There’s a limit for the number of IGs; if a buyer hits that limit, how do they know? Do we know how these different clocks fight with each other? Right now, it seems fine but a bit vague. Haven’t seen any examples of this abort signal. I don’t know if the intent of top level sellers is to inject something that I can’t see. + * Brian + * What can a buyer understand about the environment in which their bid is happening? If we have many different sellers and different sellers are applying different constraints, hard to have any idea of how the different seller constraints are going to impact the buyers. In today’s world, a bunch of publishers go through an SSP, the SSP delivers bid requests to a bunch of DSPs. They have a relationship, know what to expect. In the Protected Audience world, a bunch of stuff happening on the device on the browser locally, much smaller context to figure out the rule. With timeouts as an example, how do I set my timeouts? With some sellers, the timeout might be long, with others the timeout might be short. + * Michael + * Are you saying that as a particular interest group, you want some more global sense about what opportunity the interest group has to bid with multiple different sellers? + * Brian + * Different from what I said. When I’m putting things together as a buyer, trying to anticipate the environment in which this thing is going to be sent. How as a buyer can I tell how my campaign is going to perform across 50 different sellers? If I’m the first one in a seller’s list, I’m going to have lots of resources; if I’m #50, I’ll have less. + * Michael + * That is a control that doesn’t exist right now; no timeout that applies to all buyers collectively. + * Paul + * The only people bidding are those that have interest groups, and that might not be everyone. With today’s world, there’s a lot of play between buyers because they all go every time. But in PA, there may be less resource competition because only some buyers will have IGs on that device. + * Brian + * Different devices may have more or fewer buyers with IGs; how do they optimize with the uncertainty of this? + * Michael + * Browser signals is an opportunity to tell buyers about these kinds of things. + * David + * In the chat, Matt said that buyers are serviced in the order that they’re specified in the component seller’s auction config. Is this true for the order of component sellers in the top-level auction? + * Matt + * There is an ordering, but today there’s no global timeout. + * Michael + * If the limit is reached, it aborts the entire auction. Taking too long does not mean that some people get to go and others do not get to go, which is how it works today. + * The only constraint is that we won’t wait for a network request more than 30 seconds. If your KV server request takes longer than that, we’ll drop it on the floor and the call to generateBid will get no information back. + * Laurentiu Badea + * Time elapsed means if the page runs multiple auctions (many ad slots) the chance to complete drops proportionally. + * Matt + * We use processes for bidders and sellers, shared by all auctions running simultaneously. So, auctions can have an effect on each other. Cumulative timeout is shared. Running scripts at once in the same auction or different auctions will share the same process. + * Paul + * A lot of this is limited by how many processes we have and how many processes we share. In the last 15 years, a rise in the number of cores. In many ways, consumer devices have been limited by the single threaded nature of programs. We’re doing a better job of using all the cores in Protected Audiences. Doing more work might actually not mean more wall time. + +### Notes from chat (some captured above): + +David Dabbs: Matt is that documented somewhere in the explainer, or does one have to inspect the chromium source? Or spec? + +Matt Menke: With cumulative timeout, we don't even run generateBid, (And we don't cancel any currently running call). With cumulative timeouts, we don't hit the beginning of generate bid. + +Laurentiu Badea: https://github.com/WICG/turtledove/blob/main/FLEDGE.md#35-filtering-and-prioritizing-interest-groups + +Matt Menke: Buyers are run in the order they're specified. In the auctionConfig interestGroupBuyers array + +Roni Gordon: isn't that done in parallel? + +Matt Menke: We can't create an infinite number of isolated processes, each with its own JS thread. So there's a limit of buyers that we process at once. (On desktop - on Android, they all run in the same process, sharing a single JS thread) + +Matt Menke: My suggestion (as someone who admittedly has no clue) is just to randomize buyer order. Note that there's no inter-buyer timeout currently. So I'm not sure how important order actually is between buyers + +Harshad Mane: But Seller does not know how many IGs each buyer has so prioritizing buyers in random order may not be optimal + +Matt Menke: I'm not sure order actually matters - we do run them in the order provided, but there's no global timeout, only per-buyer timeouts. So unclear if running first is actually an advantage (or disadvantage) + +Laurentiu Badea: time elapsed means if the page runs multiple auctions (many ad slots) the chance to complete drops proportinally + +Roni Gordon: why would the time against those configured timeouts be impacted by other any runAdAuction calls? + +Marco Lugo: I did observe that with 1k IGs doing heavy computation everything broke down but that was last year before several improvements come in. Maybe an edge case but it relates to what was said. diff --git a/meetings/2023-11-08-FLEDGE-call-minutes.md b/meetings/2023-11-08-FLEDGE-call-minutes.md new file mode 100644 index 000000000..40edebfde --- /dev/null +++ b/meetings/2023-11-08-FLEDGE-call-minutes.md @@ -0,0 +1,173 @@ +# Protected Audience (formerly FLEDGE) WICG Calls: Agenda & Notes + +Calls take place on most Wednesdays, at 11am US Eastern time; check [#88](https://github.com/WICG/turtledove/issues/88) for exceptions. + +That's 8am California = 5pm Paris time = 4pm UTC (during winter) + +This notes doc will be editable during the meeting — if you can only comment, hit reload + +Notes from past calls are all on GitHub [in this directory](https://github.com/WICG/turtledove/tree/main/meetings). + + +# Next video-call meeting: Wednesday Nov 8, 2023 + + +## Attendees: please sign yourself in! + + + +1. Michael Kleber (Google Privacy Sandbox) +2. Brian May (dstillery) +3. Roni Gordon (Index Exchange) +4. David Dabbs (Epsilon) +5. Russ Hamilton (Google Chrome) +6. Orr Bernstein (Google Privacy Sandbox) +7. Isaac Schechtman (BidSwitch) +8. Sid Sahoo (Google Chrome) +9. Paul Jensen (Google Privacy Sandbox) +10. Harshad Mane (PubMatic) +11. David Tam (Relay42) +12. Stan Belov (Google Ads) +13. Don Marti (Raptive) +14. Isaac Foster +15. Jeffrey Wieland (mMGNI) +16. Neil Haack (Criteo) +17. Dmitry Stropalov (OpenX) +18. Laurentiu Badea (OpenX) +19. Matt Davies (BidSwitch) +20. Fabian Höring (Criteo) +21. Tamara Yaeger (BidSwitch) +22. Kevin Lee (Google Privacy Sandbox) +23. Matt Menke (Google Chrome) +24. Risako Hamano (Yahoo Japan) +25. Andrew Pascoe (NextRoll) +26. Marco Lugo (NextRoll) +27. Caleb Raitto (Google Chrome) +28. Abishai Gray (Google Chrome) + + +## Note taker: Tamara Yaeger + + +# Agenda + + +## Process reminder: Join WICG + +If you want to participate in the call, please make sure you join the WICG: https://www.w3.org/community/wicg/ + + +## Suggest agenda items here: + + + +* Roni Gordon + * Macro substitutions - https://github.com/WICG/turtledove/issues/817 + * API versioning - https://github.com/WICG/turtledove/issues/823 + * Sensitive signals - https://github.com/WICG/turtledove/issues/824 +* Isaac: + * Multi Tag Support via “Mixed Ranking”: (really, this + multi tag + bit leak discussion and how we can be creative) https://github.com/WICG/turtledove/issues/846 + * Buyer/Seller Reporting Questions: https://github.com/WICG/turtledove/issues/682#issuecomment-1710965068 + * Optional decouple bidding/reporting function urls to allow smaller k tuple: https://github.com/WICG/turtledove/issues/679#issuecomment-1703973736 + + +# Notes + +Michael (Chrome): We have a standing list of agenda items. Isaac to nominate a topic. + +Isaac (Xandr): I need to refamiliarize myself w reporting issues, I should go 2nd. + + +## Macro substitutions - https://github.com/WICG/turtledove/issues/817 + +Roni (Index): Issue #817, macro substitutions, this is about understanding what the expectations are from API, I suppose also from buyers, about how macros will behave. We have macro support via replacing URI / URN, my understanding it’s limited to render URL and not what ends up evaluated. I’m trying to understand – today programmatically as sellers we’re responsible for indicating clearing prices and other macros not only in the URL, and I have not seen any indication that what is generating response will understand these macros, so I don’t know where they go anymore. Is there another mechanism? + +Michael: There are two halves to the question (1) what we design and what intention is on how to use macro substitution functionality of PAAPI, (2) how that should interact w IAB expectations about macro subs ought to work. I think the clear answer is that the way we design things to work right now is not same as a set of assumptions that IAB expectations were written, so something will have to change. Won’t be a seamless perpetuation, will involve discussions w IAB and changes that ad ecosystem will need to get in on. + +You’re right that there are 2 diff types macro substitutions. First there is replace in URN function, intended to take something in k anon render URL and allows some subs in the URL that incorporate info on pub’s page. They are not about subbing info about what happened in auction, just info that is already available on pub page. No matter what you do w that replacement system, you will never get more info than what is available in standard event level reporting path, k anon render url + pub page. That’s doesn’t help w learning clearing price or store of privileged info. + +The second kind of substitution is the one that's part of the reporting APIs in which it is possible to deliberately send reports, could be set up to be sent inside of auction, and when you set up reports inside auction like reportWin or reportResult in auction, you can include macros in those URLs to sub later stuff that happened in creative rendering time or what happened as result of auction (px). Reporting API mechanism we have is where macro sub is available. + +That is different from substituting macros in body of rendered creative. No support for that right now, deliberately. Rendered creative does not have any imposed restrictions on what domains resources can be loaded from or where info can be exchanged with. But the combo of info about things like what happened in the auction, that is much more sensitive info, that we want to only send to parties making attestations that they will not circumvent Privacy Sandbox restrictions. We want to only allow this macro substitution going to domains that are signatories to attestations, therefore do not want to allow inside rendered body of creative. Would prefer to remain k anonymous and not follow secret info so that we don’t need to worry what domains are contacted from inside rendering frame. This is essence of why Protected Audience API makes a distinction between rendering and reporting. In the ads world today, when the creative is rendered, you’re also doing reporting at the same time, there is no difference between rendering activities for putting pixels on the screen and ones for telling servers what happened. In PA auction we separate those two things and treat them differently. Screen has to be k anonymous, can load stuff from arbitrary domains, but can’t get lots of extra info as a result. The stuff that needs info about auction outcomes should go to reporting flows. + +Roni: Let’s say today auction price macro is included, what I’m hearing is there is intentionally no facility for replacing that macro as a seller, it will remain unreplaced and that is something that the buy side will have to deal with. In other words OpenRTB spec requires replacement, even if with nothing. I can’t do that as a seller. Therefore buyer’s macros cannot involve what the API will not involve me to replace. Signaling needs to be outside creative / markup. Is there a signals for winner between report results / report win, so I believe if we wanted to signal that we would need to devise a standard for meta data. We would need to talk about mechanism for communicating these macros between report result / report win. Onus could then be on report win buyer to make subs on their own. Is that a fair assumption? + +Michael: Almost right, it is still possible to get the reporting that you want from inside the rendered creative, but not by loading some URL as part of the markup. The mechanism we have where party that shows up in rendering flow can get info involves their use of fenced frame that reporting API. So inside rendering there is new JS API that says send this report to this URL, that report can include macro values in it, the browser will automatically sub them w values that come from the outcome of the auction and the values that are provided by buyers / sellers, that whole flow is possible but it doesn’t involve loading HTML, gotta call JS API. In addition, the domain has to be explicitly listed along w the creative by the buyer as a domain it is ok to send reports to. That domain has to be done the attestation work to be allowed to get info from auction, so as not to circumvent Privacy Sandbox. + +Roni: Even w/in context of today iFrame example, even if pathed signals from winner, still no facility for IFrame to replace that macro even if buyer has that info. + +Michael: Correct, doesn’t get replaced through HTML, has to go through reporting flow. + +Roni: To summarize, any expectation that buyer has that macro will be replaced in markup, are false assumptions now, that will never happen. If they want to do it the “new way” they report event and all requirements. If they want to use event level reporting, there needs to be an agreed upon meta data signals for winner between report results and report win. That’s where we’re at. + +Brian (Distillery): The implication is that formerly evidence of what happened on browser, result of having my macro pixels subbed and sent to me, is now going to have to have to be presented in another way. We have a fenced frame where something is happened, trusting what is happened, reporting coming from another channel, I’m concerned we are not getting strong signal that what we paid for is what we bought. Should we think about some means of producing signals to buyers to provide evidence of the thing getting billed for? Can we do it in privacy preserving way? + +Michael: It has always been the case that any info received about browser was because browser sent request over network. The different now is instead of one request, there’s two types of requests. Some of them are regular old HTTP requests for rending things, using pixels, those still exist. They don’t have macro sub involved in them. Macro sub was always a black box, some man in the middling HTML to insert stuff into it, asking unknown party to change what you sent. What is in the browser is exactly what you send now. Before many unknown people could mutate HTML before it gets rendered, now there are not. From that POV a lot less behind the scenes black box happened in this model. + +Brian: In old model, when event happened, details associated happen at same time. Now they will be provided at a later time? In my pixel fires it can have context from which it fired in macro substitution, at point at which it fired. When I have to go through diff channel, I have to assume the event that happened to produce info, but w/out direct evidence. Later I’ll get info about that pixel fire. + +Michael: Not how I think of it. There were always different events. + +David (Epsilon): Just to clarify, embedded in there, there won’t be an impression time YoY, won’t be an impression time fetch creative because vision for fenced frames is there is not network, right? + +Michael: That was our vision early in this effort, but not any more. Infrastructure required to realize old vision is not how the web is moving. When we started this journey and described this journey in 2020, we liked the idea of rendering with web bundles and without using the network, but it seems that is no longer likely. However it is still the case that the place where we should get to in 2026 is one where even though a creative that renders loads over the network, it should have very little info inside the fenced frame. What we just talked about is fenced frame doesn’t have info on clearing px of auction. That info is known inside auction but tries to be careful about releasing info that could be used for tracking. The vision for the future of fenced frame is one where it is still possible to render creative, but the creative has little information beyond the fact that it rendered. A pixel can reveal that this showed up on a screen, but it would be great if it didn’t reveal info about person who saw the ad. Anything that involves cross-site info, sense of user’s ID, that kind of info should go through dedicated reporting API with privacy protection built in. This means something about aggregation, differential privacy noise, delaying time at which things are sent. Fenced frame rendering is not a privacy preserving API, just a boundary to not allow user info to flow through pub page. + +Roni: Getting back to original ask for clearing price. I understand the report result / report win, gap is in decision - what price to use, conversions from net to gross - all of it based on pub host name and rendered URL and associated meta data. If I make decision that changes bid price, no way to get it into report result, no way to tell what I need to tell to buyer. Today there are mechanisms for leveraging auction px macros to provide fee reductions and buyer incentives. They may not charge you full bid submission, I can communicate the discount. No way to change $10 to $9.50 because Scoread could not send info to report result because of privacy. There’s a gap where the info I know is in Scoread. Modified bid is about the net price, not the buyer’s price. MOdified bid is to be used in top level seller auction. There are 2 parties in this transaction, and those prices can be modified at any point. + +Michael: The intention is that the person running the auction gets to output - how much pub should get paid, how much advertiser should get charged. We want to make it possible for you to make those decisions and communicate them at reporting time. Is there a third number that is different? + +Roni: There is, so the take rate adjustment is modified bid. Both bid and modified win are available, but that doesn't communicate to buyer what they were charged. They might submit a $10 buy I may not charge them $10 independently of my take rate. Submit $9, actually charge $9.50. I don’t have a way to signal that piece, because all I have is incoming bid from buyer, and modified bid going to seller. Only known in Scoread if I give buyer discount. No way to indicate it to report result. + +Stan (Google Ads): You’re saying some discount could be based on individual creative + +Roni: Assume buyers will make decisions based on render URL, exact same on sellers side. Right now no trusted scoring signals going into report result. What I now in Scoread should be in report result. The real question is, can I get trusted scoring signals in report result? + +Michael: [Sorry, my Chrome crashed, back now but I missed some of the conversation.] + +Paul Jensen (Privacy Sandbox): I just want to understand, do they know they will pay lesser amount? + +Roni: Not at auction time + +Paul: How does the seller know who to give discount to + +Roni: Based on pub host name and render URL, the info Scoread has. All discounts handled post-auction. + +Paul: But in 2nd price auction you would know? + + +Roni: There are no second price auctions. + +Paul: Trying to understand how / what is the discount that the buyer knows is coming, they’ll bid not knowing there’s a discount? + + +Roni: Bidding behavior is not based on my margin. They bid in gross terms before all adjustments. Separate from how buyers / sellers pay and charge, we can make intentional incentive programs based on volumes. The question is, all the info that we need is in the trusted scoring signals. If they are ok for Scoread, I assume they’re ok for report results, and it’s just not there because we didn’t come up yet. + +Michael: It seems there shouldn't be any new privacy problem in giving you the info you’re asking for because it’s the signals that are fetched from sellers key value server based on looking up render URL and the host name of pub site, and you’re taking about making those signals available in report result, an environment that already knows the render URL and the hostname in question. Yuu knew keys and you wish you knew value. Browser has already looked up value based on those keys. We’ve never piped it through to get you that info where you want, but I think that's just because nobody has asked for it until now. I don’t see a reason why it can’t be there. + +Matt (Chrome): If all info in request to seller could be used to generate response, could potentially learn other data. Data has to be generated on per URL basis. + +Michael: What Matt is saying, if the key value server is behaving like expected (only based on each individual URL plus the host name), then no new privacy problem to make it available in report results. If owner of key value server is cheating and packing info in response for one URL based on other URLs, then would be channel for leaking cross-site info. Someone who wants to cheat in their key value service by processing as one big thing. + +Isaac (Xandr): presumably you can cheat by combining render URLs but if you export… attestation … (?) . It could happen but it’s covered by the same thing, I don’t know. + +Matt: My understanding is that discussion around info from same joining origin in signal call, which would break anonymity. So we’d have to revisit whether we allow key value server to process all seller URL from the same joining origin. Also note it does not apply to bidder key value info. + +Michael: The only reason that giving what Roni is asking for is feasible is because URL is already k anonymous. Not a collection of keys that buyers key value server, the already k anonymous render URL. + +Roni: Made progress, solidified understanding. + +Michael: It didn't occur to us that it’s a valuable thing to have. + +David: Roni would it be fair to describe this as 1st class feature request/ Now that we seem to have alignment, we were actually talking b4 meeting about relaxing single origin. How do we go about saying we’d like you all to have 1st class feature request? Anything special we need to do process wise? + +Paul: We have a GitHub label for features that don’t break things, it could go there. + +David: So we have to tag one of you to put it there? Fine, good to know. + +Kevin (Privacy Sandbox): I’ll let you know + +Michael: Scoread still has more info that report result does, even with this change. Scoread can get lots of info handed to it from Generate Bid. Generate Bid has arbitrary 2-sites info about user. Still the case that could violate privacy. This particular info that report result can get we know is based on k anonymous data, not on individual user tracking data. + +Laurentiu (OpenX): Not necessarily based on K values, could be based on some machine learning or computational intensive work. + +Michael: the fact that we can’t give you full Scoread contents. I’d be delighted if this doesn’t introduce privacy problem. diff --git a/meetings/2023-11-15-FLEDGE-call-minutes.md b/meetings/2023-11-15-FLEDGE-call-minutes.md new file mode 100644 index 000000000..134f30f09 --- /dev/null +++ b/meetings/2023-11-15-FLEDGE-call-minutes.md @@ -0,0 +1,250 @@ +# Protected Audience (formerly FLEDGE) WICG Calls: Agenda & Notes + +Calls take place on most Wednesdays, at 11am US Eastern time; check [#88](https://github.com/WICG/turtledove/issues/88) for exceptions. + +That's 8am California = 5pm Paris time = 4pm UTC (during winter) + +This notes doc will be editable during the meeting — if you can only comment, hit reload + +Notes from past calls are all on GitHub [in this directory](https://github.com/WICG/turtledove/tree/main/meetings). + + +# Next video-call meeting: Wednesday Nov 15, 2023 + + +## Attendees: please sign yourself in! + + + +1. Michael Kleber (Google Privacy Sandbox) +2. David Dabbs (Epsilon) +3. Harshad Mane (PubMatic) +4. Luckey Harpley (Remerge) +5. Sven May (Google Privacy Sandbox) +6. Paul Jensen (Google Privacy Sandbox) +7. Isaac Foster (MSFT Ads) +8. Maciek Zdanowicz (RTB House) +9. Kevin Lee (Google Privacy Sandbox) +10. Orr Bernstein (Google Privacy Sandbox) +11. Roni Gordon (Index Exchange) +12. Leeron Israel (Google Privacy Sandbox) +13. David Tam (Relay42) +14. Ning Hu (MSFT Ads) +15. Fabian Höring (Criteo) +16. Russ Hamilton (Google Privacy Sandbox) +17. Jeroune Rhodes (Google Privacy Sandbox) +18. Sid Sahoo (Google Chrome) +19. Andrew Pascoe (NextRoll) +20. Marco Lugo (NextRoll) +21. Alonso Velasquez (Privacy Sandbox) +22. Abishai Gray (Google Chrome) +23. Caleb Raitto (Google Chrome) + + +## Note taker: Kevin Lee + + +# Agenda + + +## Process reminder: Join WICG + +If you want to participate in the call, please make sure you join the WICG: https://www.w3.org/community/wicg/ + + +## Suggest agenda items here: + + + +* Matt Menke + * I’ll be missing today’s meeting, but just wanted to let folks know I’ve uploaded a draft of how ordering and cumulative timeouts tie into auctions. It’s available here: https://github.com/WICG/turtledove/pull/906 +* Isaac: + * Would like to revisit, briefly hopefully, the Dynamic K-Anon Selection feature, in particular w/r/t to the “prioritization reasoning” offered earlier this week [here](https://github.com/WICG/turtledove/issues/729#issuecomment-1807521752) + * Buyer/Seller Reporting Questions: https://github.com/WICG/turtledove/issues/682#issuecomment-1710965068 + * Multi Tag Support via “Mixed Ranking”: (really, this + multi tag + bit leak discussion and how we can be creative) https://github.com/WICG/turtledove/issues/846 + * Optional decouple bidding/reporting function urls to allow smaller k tuple: https://github.com/WICG/turtledove/issues/679#issuecomment-1703973736 +* Roni Gordon + * API versioning - https://github.com/WICG/turtledove/issues/823 + * Sensitive signals - https://github.com/WICG/turtledove/issues/824 +* Luckey + * Any plans around loss reporting for buyers? + * Note: it is much more important for us to know the market price, rather than our losing bid. + * https://github.com/WICG/turtledove/blob/main/FLEDGE.md#54-losing-bidder-reporting +* Harshad Mane + * When an adslot backs various sizes like A, B, and C, the PA only backs one size at a time. It seems that Chrome has the ability to check the available IGs on a device and the supported ad sizes for each IG in this situation. Who chooses the size for a protected audience auction (PAA)—the Publisher, Top Seller, or Chrome internal logic? Can the Privacy Sandbox standardize the supported size consistent in PA to prevent potential data leaks caused by random ad sizes that could identify a user? https://github.com/WICG/turtledove/issues/908 \ + +* David Dabbs + * `updateURL` ability to update `userBiddingSignals` \ +https://github.com/WICG/turtledove/issues/760 \ +Intending to land this in M120 [when Mode B testing begins](https://bugs.chromium.org/p/chromium/issues/detail?id=1498491#:~:text=M120%20where%20Mode%20B%20experiments%20will%20begin)? + + +# Notes + + +## Matt Menke has written up a documentation on ordering, timeout, and flow control of the auction + + + +* https://github.com/WICG/turtledove/pull/906 just a pull request so far +* Please take a look and provide feedback where necessary + + +## Isaac - Dynamic creative rendering (k-anon) + + + +* Isaac - Ran an experiment with 24 hours to see how many ads would be blocked by the requirement to only add new ads during the daily update. + * The % of impression traffic is not high. + * Wrote a script for a shorter window, but didn’t help. + * Want more numbers or different numbers. +* Kleber - If you use a daily update mechanism to get creatives into rotation, and when the creative comes into instance, there is a rollout period of 24 hours that ramps up from 0 to 100% over a course of a day. Is what you are asking looking into how long it takes for ads to become available in production. + * Result: 0.2% of potential impressions were impacted by the 24-hour delay. 6-figures volume. + * It doesn’ mean that no ad wasn’t shown, but they may have been shown an older ad. + * There may not be a revenue loss, if a bid was made on another IG. +* Isaac - the revenue loss can be 0 or some loss. +* Kleber - Not the highest priority during the testing period. +* Kleber - Is the real issue how long it takes a creative to get into production + * making the dailyupdateUrl intervals shorter could help? +* Isaac - reducing it to 4 hours helps but not as much as we want + * Continuous vs smaller window +* Paul - Is the question rate of ads into the browser? +* Isaac - Impact on customers and systems + * Dealing with all the endpoints at a browser scale may be challenging +* Paul - Want to learn more about what kind of these ads. You can push an ad instantly to a user with joinAdInterestGroup +* Isaac - Does not feel that’s correct +* Paul - Can use joinadinterestgroup and show an ad instantly + * Ask: What kind of ads are these? Estimated latency? Is there a large delay? What kind of campaign would want an immediate push? Black Friday? +* Isaac - In adtech it’s hard to delineate all use cases of the buyers + * It’s hard to plan a flash sale a week out. + * Would diving into specific cases help? + * Also, the experiment was on create. Will need to look into IG update. + * The market wants to respond quickly, and doesn’t want to wait 24 hours. + * On the publisher page, refresh the creative since it’s based on information that is no longer available on the page. + * It would also make it hard to debug a distributed system +* Roni - Wants to reiterate on a Black Friday, and other days that advertisers deal with. +* David Dabbs - Potential solution to push ads out - if the IG was grouped by origin, would it mess it up? + * If you join on a publisher site, and try to update it on another site. +* Paul - If you join an existing one, it can be quicker. + * Mask becomes big -> Add the user to a mask IG -> Serve mask ad +* Kleber - Isaac's issue mentioned the idea of dealing with privacy risks by triggering the update from an environment with limited information, and a natural place to do that would be the k/v server. + * K/V already returns info for each IG. Used now for priority vector. + * If we add something to the K/V response, a special keyword, saying “please update this interest group.” + * Would that address your needs? +* Isaac - Somewhat. Update request rate might be too high. And it may stress the update endpoint. Looking for something like append rather than an update. +* Kleber - Smallest incremental change that could help would be the k/v server method. + * K/V will eventually be in a TEE. + * While update goes to a regular server. +* Isaac - Challenge of scale is real + * If we need another endpoint for updates, it’s significant amount of traffic, and it’s a higher operational burden + * Will look further into create vs update + + +## Luckey Harpley - Loss reporting + + + +* Luckey - Talking to the Android PA team + * Looking for guidance on the Chrome-side +* Paul - Take a look at the loss reporting + * You can take a look at the documentation for `contributeToHistogramOnEvent()` for aggregate loss reporting. + * For event-level loss reporting, take a look at the `sendReportTo()` + * Downsampling is a mechanism we are looking at. +* Luckey + * Will take a look at the docs for aggregate reporting. +* Kleber + * Update the docs to point to the loss reporting doc + * AI: Kevin Lee + * Link: https://github.com/WICG/turtledove/blob/main/FLEDGE\_extended\_PA\_reporting.md + + +## Harshad Mane - Ad slots on the pages support multiple sizes + + + +* Harshad - Who is deciding the ad size that’s picked + * Is it the publisher through the config, or the top-level seller? + * Or is it Chrome? Looking at the available IG groups, and decide which ad supports it + * Who is the decider? +* Kleber - When you run a PA auction, the only output is “yes there is an ad” or null. + * PA does not give any info out about how to render the ad + * PA does not offer any info about the size of the ad + * Therefore, the person who needs to know the ad size, is the person that renders the ad + * The party that takes the output and create a tag, and place the tag in the DOM + * That party needs to know how much space the ad will take up + * Either the publisher is picking a single size, or the party running the script on the publisher page, (most likely the top-level seller), and render it. +* Harshad - IG has ads with 300x250 + * Ad slot 720x90 + * The IG does not have that ad size available. + * The size selection logic should be looking at the available ad sizes. + * And when it’s not there, who decides what is chosen? +* Kleber - The width and height of the outcome are specified in the auction configuration + * That is how the auction learns about the size to render + * Why can’t size be chosen by looking at all the IGs + * Because all the IGs available are secret cross-site info that we are trying to not leak. + * If we allow size to leak, then information can be encoded into the size dimensions +* Harshad - However, they can’t access those IGs, so they don’t know which sizes can be used. Chrome is the only one that can look at the sizes. + * Why shouldn’t Chrome pick the size +* Kleber - This is similar to a scary idea that was suggested in the multi-size ticket + * Let the browser determine the slot size, BUT with appropriate noise + * https://github.com/WICG/turtledove/issues/825#issuecomment-1764604908 + * However, if we do this without a lot of care, then it will be an info leakage vector + * The only way the browser can possibly do this is to start a research effort to see if it’s possible for the browser to pick a size that is private, along with involving randomness, and it would also mean the size would not always be the highest bid because that would be a leakage vector. + * Therefore, it sounds like a hard problem for the browser to contribute information to. +* Harshad - If random size be a concern, + * Then can PS provide a list of standard sizes to support? +* Roni - Separate two different problems: (1) who chooses size vs (2) order of operations + * Only the iframe creator knows the size of the iframe which would be the top-level seller. + * However, the top-level seller config is created after all component auction configurations. + * And during the contextual auction, the buyers need to be notified of the size. + * The buyers need to return a perbuyersignals for every size. +* Kleber - How does Prebid know about the ad size that can be filled today? + * How do you learn that? +* Roni - Ad slots are configured with ad sizes + * That’s sent to the contextual auction + * Sizes are communicated to the buyers + * It’s made available to the contextual auction +* Kleber - Does it come from the publisher? +* Roni - The final size is only available at the end of the auction when it’s finalized. + * I know the superset of the sizes, but does not know the final size + * Neither does the publisher know +* Kleber - Taking PA as it is now, as a black box that renders a single size, the question is, is there an info channel that lets the top-level seller know that “this is the size” that the PA auction should look at, and that size can be fed into the contextual auction, and the signals that feed into the PA auction could focus on that size, since that is the only size that’s chosen. + * When is the point of time that size is available on the page +* Fabian - Having the same PB+GPT integration issue + * Get multiple sizes for ad requests for a seller + * Want to look for a path that integrates with PB and GAM to allow the final size to be passed into the PA auction +* Kleber - This really depends on how the top-level seller chooses what the right size is. + * Something like "First item in the size list" would make it easy + * Something like "Using a ML model to choose the optimal size" would make it hard + * May be somewhere in the middle, I don't know +* Fabian - Similar to the currency + * In this case, the buyer chooses + * Buyer sends back the ad sizes chosen during contextual auction +* Roni - Currency you can switch back. + * And the buyers pass the size info through perbuyersignals +* Kleber - Hard to answer this question without any GAM person on the call today, because we don’t know when the size information becomes available. +* David - Has PR supporting sizes land? +* Paul - Yes, it landed +* David - There can be different size bundles. Are the ad sizes passed in a macro? +* Kleber - We provide macro substitution if the server needs to find out what the size +* Roni - Replaced by the size of the iframe, and not the size of the ad +* Kleber - Is there a GitHub issue on this? +* Roni - Harshad just opened up (#[908](https://github.com/WICG/turtledove/issues/908)) and it was mentioned in another issue. +* Kleber - Will talk to the GPT folks and see if this question needs to be moved over to their repo, and will investigate into the information flow. +* David - GPT supports width and height. It may be a way to pass a size. + +## David Dabbs - updateURL ability to update userBiddingSignals #760 +* Paul - Implementation is close. It would be M121. + * Kleber - Merging into an old branch is for bug fixes, and new features would go on a new branch. + + +## No Meeting Next Week (Nov 22) + + + +* Kleber - Next week is the day before thanksgiving. + * Usually low attendance + * My instinct is to cancel. + * Vote for canceling. +* Isaac - Would love it for just to be us, but would also like to include the industry +* Kleber - Nov 29th will be the next time we meet. diff --git a/meetings/2023-11-29-FLEDGE-call-minutes-slides-ab-testing.pdf b/meetings/2023-11-29-FLEDGE-call-minutes-slides-ab-testing.pdf new file mode 100644 index 000000000..1043989e7 Binary files /dev/null and b/meetings/2023-11-29-FLEDGE-call-minutes-slides-ab-testing.pdf differ diff --git a/meetings/2023-11-29-FLEDGE-call-minutes.md b/meetings/2023-11-29-FLEDGE-call-minutes.md new file mode 100644 index 000000000..f74da115c --- /dev/null +++ b/meetings/2023-11-29-FLEDGE-call-minutes.md @@ -0,0 +1,185 @@ +# Protected Audience (formerly FLEDGE) WICG Calls: Agenda & Notes + +Calls take place on most Wednesdays, at 11am US Eastern time; check [#88](https://github.com/WICG/turtledove/issues/88) for exceptions. + +That's 8am California = 5pm Paris time = 4pm UTC (during winter) + +This notes doc will be editable during the meeting — if you can only comment, hit reload + +Notes from past calls are all on GitHub [in this directory](https://github.com/WICG/turtledove/tree/main/meetings). + + +# Next video-call meeting: Wednesday Nov 29, 2023 + + +## Attendees: please sign yourself in! + +1. Michael Kleber (Google Privacy Sandbox) +2. Roni Gordon (Index Exchange) +3. Sven May (Google Privacy Sandbox) +4. Orr Bernstein (Google Privacy Sandbox) +5. Paul Jensen (Google Privacy Sandbox) +6. Brian May (dstillery) +7. David Dabbs (Epsilon) +8. Fabian Höring (Criteo) +9. Harshad Mane (PubMatic) +10. Maciek Zdanowicz (RTB House) +11. Russ Hamilton (Google Privacy Sandbox) +12. Kevin Lee (Google Privacy Sandbox) +13. Wojciech Biały (Wirtualna Polska Media) +14. Miguel Morales (IAB Tech Lab) +15. Pawel Ruchaj (Audigent) +16. Jeroune Rhodes (Privacy Sandbox) +17. David Tam (Relay42) +18. Abishai Gray (Privacy Sandbox) +19. Youssef Bourouphael(Google Privacy Sandbox) +20. Andrew Pascoe (NextRoll) + + +## Note taker: Orr Bernstein + + +# Agenda + + +## Process reminder: Join WICG + +If you want to participate in the call, please make sure you join the WICG: https://www.w3.org/community/wicg/ + + +## Suggest agenda items here: + + +* Isaac: + * Sec-cookie-label for no label: https://github.com/GoogleChromeLabs/privacy-sandbox-dev-support/issues/153 + * Buyer/Seller Reporting Questions: https://github.com/WICG/turtledove/issues/682#issuecomment-1710965068 + * Multi Tag Support via “Mixed Ranking”: (really, this + multi tag + bit leak discussi loop on and how we can be creative) https://github.com/WICG/turtledove/issues/846 + * Optional decouple bidding/reporting function urls to allow smaller k tuple: https://github.com/WICG/turtledove/issues/679#issuecomment-1703973736 +* Roni Gordon + * API versioning - https://github.com/WICG/turtledove/issues/823 + * Sensitive signals - https://github.com/WICG/turtledove/issues/824 +* Fabian Höring: + * Protected Audience AB testing (https://github.com/WICG/turtledove/issues/909) 10 min prez + discussion + + +# Notes + + +## Protected Audience AB testing from a DSP perspective - https://github.com/WICG/turtledove/issues/909 + + + +* Fabian Höring: + * Slides presented: https://github.com/WICG/turtledove/blob/main/meetings/2023-11-29-FLEDGE-call-minutes-slides-ab-testing.pdf + * Why do we need AB testing? + * Increase performance of PA + * Measure technical changes - when we roll out stuff on 1% of users and make sure we don’t break anything + * Measure stuff in a consistent way + * If we can’t do AB testing the right way - for e.g. sales and conversions - cannot improve the performance of PA + * User scenario + * User browsing shoes / browsing phones / visit his famous blog / checks the weather / clicks on shoe ad / buys shoes. + * With AB testing, split the the user into two populations: A and B + * Split by user - currently using 3rd party cookies. Consistency exposed to A or B behavior. Can change the bidding behavior, and then check to see if this user had more sales/less sales. + * Throughout the user scenario, always in the same group. + * With first party on publisher site, in some websites, exposed to the A group behavior, in other websites, exposed to B group behavior. Not perfect because you don’t see the full impact of being in a group because some publisher pages would be on the “other” group. + * With 1st party on advertiser, then different campaigns for the same user would end up in different groups. + * Existing mechanism with ExperimentGroupID + * Can assign an ExperimentGroupID, and based on this ID, decide if the user is exposed to population A or B. + * Cannot be assigned by the buyer anymore, can only be assigned by the seller. + * In this scenario, could attach the first party user ID to the interest group. Could decide based on this realtime call if we are exposed to population A or B. Could encode when we get render URLs which population we chose. + * In this scenario, cannot handle as much + * If we split by tagging, could get some bias. + * Could get leakage, as the same user will change for different advertiser websites. If the advertise has two campaigns, can see if if one campaign works better than the other. + * Proposal: inject a global Chrome ID, reduce entropy as much as possible and not leak too much information to negative side effects to identifying the user. + * Could use 3 bits - which would allow for 8 groups. + * User behavior could be impacted by being a member of a group; constantly rotating the population helps to mitigate this. + * Reporting + * For each population, can look at e.g. clicks vs sales. + * In the short term, could use existing mechanisms - e.g. having more renderUrls, and using one based on the population. But all of these things have privacy mechanisms in place, so always come with some tradeoff. + * Fully compatible with aggregated reporting (with DP noise). Can still bucketize with population. +* Kleber + * Two different types of discussion we could have here, and I would like to do both: + * Understanding better - asking questions about whether the existing capabilities really don’t meet your needs, or + * Talking about the proposal to introduce more bits. +* Brian May + * Would like to better understand where the confounding factors are in the current model, why AB tests can’t be utilized as they currently are given PAAPI constraints. +* Kleber + * Pick A vs B impression by impression - no consistency + * Pick A vs B using 1st party identifier on publisher site - different treatments for the same user when on different sites. + * Makes sense why this doesn’t do a good job of letting an advertiser know the relative effectiveness of different ad campaigns, because same person might have seen both. + * Pick A vs B at the time you put the user into an interest group. Now you have separate A and B populations. IG would contain renderUrls which indicate whether you were in A or B, which means you do pay a factor-of-two cost in your k-anonymity counts. It seems this does let you do consistent experimentation of different bidding models on different ad campaigns. +* Fabian + * Too much noise to distinguish whether A or B is better. In some scenarios, it could work, if you have one large advertiser testing A vs B on their own campaigns. If one advertiser wants to test, it could work. But if you want to get raw events, means you would need to split on all traffic. Means you need to change behavior of all campaigns. If you split ads, you could see both behaviors. For e.g. adding a button - users may like that and click on those more - then it works. For bidding behavior where there’s no visual impact, can’t necessary see this - e.g. if there was a case where both A and B were shown, how do you know if it was because B was shown or because both were shown? +* Kleber + * So this is the case where a DSP wants to run an experiment on something like the bidding model across all advertisers. Not enough traffic from a single advertiser. Want enough data that you can tell the difference between things you want to tell the difference between. + * But, if you’re a user and you’re seeing some ads with A and some ads with B, then why isn’t this enough? You can’t control all the ad campaigns that a user will see, because some are run by other DSPs, so even when you’re changing your bidding model, you’re only affecting the ads affected by that bidding model. +* Fabian + * Don’t have an example right now. The signal you’re using for bidding is not a simple signal. If you cannot expose a user consistently to the same behavior. +* Kleber + * What I was expecting to hear is, what if you add a user to an ad campaign if they’re on different websites. Different advertisements from advertisers joined on different sites, then could get lack of consistency. Then I would say, negative targeting on interest groups thing could be part of a solution. +* Fabian + * Scenario is mostly a retargeting campaign. For upper funnel campaigns. +* Paul + * If you have a three-bit identifier, still breaks down if you have other buyers involved that won’t respect another person’s campaign. +* Fabian + * But only measure what Criteo does. Only want to modify my bidding behavior and optimize my bidding algorithm. +* Paul + * 3-bit mode, or split by 1st party advertiser, or by 1st party publisher. Wondering what the efficacy of each is. +* Fabian + * Can do a technical AB test on experiment group ID. Just want to know that it’s not completely broken. Don’t care about noise or confidence intervals. For this, group ID is enough. + * User ID is perfect for single-advertiser campaigns, where one advertiser wants to propose a single advertiser campaigns, where one advertiser wants to propose a different creative to see how they behave. Lots of noise. +* David Dobbs + * Might some constrained controlled application of shared storage work in the bidding worklet? +* Fabian + * No, because shared storage cannot be used inside FLEDGE. Also checked this out; shared storage can be used after rendering for creative AB tests. But not completely random, could be aligned with the 3 bits. Every time you use shared storage selectURL API, you can leak three bits. This is why three bits. Shared storage is even worse, because it’s 3 bits for each time you call. Vs these three bits - it’s the same three bits every time. Budget capping mechanism. +* Kleber + * For enabling shared storage in FLEDGE, if we do this in a reckless way, could enable a lot of tracking. Would need to be done with care, where you could maybe access only a few bits of information from shared storage. Can’t give an answer, but could give it some thought. +* Brian + * What if the browser had essentially one bit. Bidding algorithms could have one bit. If the bit is 0 don’t bid, if the bit is 1, do bit. Some level of consistent audiences. Some way for the browser to divide the audiences, without the advertiser seeing the browser level split. +* Kleber + * Seems unlikely that a single bit will remain a single bit. Seems likely that that the three bits are needed. And there’s a slippery slope, in which this could later become 6 bits or 9 bits. Could add noise, but then each bit is less useful and you need more bits. Tradeoff mechanisms we’ll have to talk to researchers about. Noise would make your confidence intervals larger. Not an easy thing to answer, going to have to think hard to think about this. And why things that are already possible - e.g. A/B split at the advertiser level - A vs B version of an interest group - has some advantages. It just works. That’s Brian’s “I just want one bit”; just do that at interest group time. As soon as you want things to be cross-site, you have trade-offs against noise. People like Charlie and Josh who have thought a lot about differential privacy and noise on the shared storage side of things will have a lot of thoughts, but they’re not in this meeting, so we can’t come to any conclusions. + + +## API versioning - https://github.com/WICG/turtledove/issues/823 + + + +* Roni + * Short version - Paul and others are very active about getting changes into everything. B&A services has a version of this. So do a bunch of other surfaces. We’re in the middle of changing things. We’re changing which headers are required to be returned. Subresource bundles to other HTTP headers. Additional bids from negative targeting. No easy way to know when things are added programmatically. Neither client nor server have a way of knowing what’s going on. Even release notes don’t align with all of the changes, and there’s no way to introspect that programmatically. Basically, what support exists for what origin. And when can we expect a stable API surface, not have to write code to inspect this. +* Paul + * I share the goal of a stable API. We want to make changes, and it’s going to be a living API in that way. There’s going to be a need to feature detect what thing is there. Versioning is not that easy. A lot of the way we launch these is 50/50 on canary/dev, then 50/50 beta, then 1% stable. Necessity to make sure we’re not breaking things as we roll them out, so not as easy as “what version is this”, because there’s a bunch of things rolling out at the same time. The explainer/spec are being updated as we update things as well. Because there are a few things being rolled out at a given point in time, there is no one version number that will tell you the answer. Knowing which 50/50 branch you’re in in dev/canary or beta, or which 1/99 branch you’re in on stable. + * So, to help with this, I published a document https://github.com/WICG/turtledove/blob/main/PA\_Feature\_Detecting.md that shows - for everything we’ve launched since the APIs reached General Availability - how to detect for any one of the things that you can use it. Once it’s at 100% stable, then at tip of tree, you can expect that it’s there. But until then, feature detection will be necessary. + * The other thing to keep in mind is that we’re trying to keep things as stable as possible, and avoiding breaking backwards compatibility. Deprecating things on the web is pretty hard. All of the things we launched at GA, and all of the things we added in the origin trial over its 15 months, we added in an optional way. It’s very rare that the web breaks backwards compatibility. Many of our APIs are dictionaries of, “what do I want to turn on”; you can add new thing that the browser doesn’t know about yet. + * In short, feature detection is a good practice to know what’s available +* Kleber + * Or ignore new features. If you keep using the API the way it was, it’ll still work. That’s what the goal of maintaining backwards compatibility is all about. +* Roni + * I get it, but maybe we’re not being complete about what feature detection means. Some of these things have an impact on sellers or buyers. Some of the KVs get parameters; if they’re missing, I know they’re missing, but not everything. If I as a seller want to know that negative targeting is enabled, or size on the kAnonymity checks. Because I have to generate my auction config somewhat independently of what the browser is doing, I have to build something that can get sent from client to server, need to know about capabilities. I don’t have a list of everything that needs to be detected. +* Paul + * Some of these things are harder than others, e.g. sizes and knowing whether that’s in the kAnonymity check. We’ll work harder to better surface what features are enabled to different parts of the auction. +* Kleber + * Definitely note to us when we there are gaps and we can try to +* Brian May + * Roni’s concern is a symptom of an underlying concern that I’m hearing regularly, in which we have an extremely complex machine we’re not familiar with, where we’re trying to make sense of it, poking it and we have a requirement that we do some assessment of this machine vs our current status quo, and how changes in the model will impact our businesses. We’re not sure what this thing is, we have a couple different expressions of the model in various states of completeness and it’s regularly getting updated and changed. The challenge we’re facing is that we need to figure out how it works before we can begin to figure out if it works and we’re also facing testing deadlines. +* Kleber + * I hear what you’re saying, but this weekly meeting alternates between people requesting a stable machine and here are 5 new feature requests. +* Brian + * I understand, but we need to get to a place where we can keep it stable long enough to figure out how it works so we can do that assessment. +* Kleber + * During the CMA testing phase in the first half of 2024, good time for a relatively quiet period, not rolling out substantial changes. But that said, the web is a dynamic platform, nothing is written in stone, things constantly changing. +* Brian + * Another concern is that we slow down on development, and when we’ve come up with conclusions based on that, we make significant changes to the APIs, and all the things we figured out about business impacts are invalidated. +* Kleber + * Hope is that your prior evaluation should be a lower bound on how well this API works for your needs. From testing, are hoping to get feedback on ways to improve the API to make it better satisfy your needs. +* Brian + * Can we get help from the Chrome folks on how to test these things, things like feature detection. +* Kleber + * Yes, e.g. Paul’s doc, in the notes and chat. +* David + * Release notes stopped being updated at M114. If one were to read the spec or the human readable version, does that reflect what’s going to be live at M120, e.g. in mode B labels where the cookies are being taken away, which is where people trying to get consistent numbers will be testing. +* Kevin Lee + * Alonso and I are working on improving change logs. +* Paul + * FYI, the explainer will often have things that are not yet shipped, since we need to make these changes as part of the Chrome release process for IntentToShip. +* David + * More clarity/transparency is a good thing. diff --git a/meetings/2023-12-06-FLEDGE-call-minutes.md b/meetings/2023-12-06-FLEDGE-call-minutes.md new file mode 100644 index 000000000..e79b88ced --- /dev/null +++ b/meetings/2023-12-06-FLEDGE-call-minutes.md @@ -0,0 +1,232 @@ +# Protected Audience WICG Calls: Agenda & Notes + +Calls take place on most Wednesdays, at 11am US Eastern time; check [#88](https://github.com/WICG/turtledove/issues/88) for exceptions. + +That's 8am California = 5pm Paris time = 4pm UTC (during winter) + +This notes doc will be editable during the meeting — if you can only comment, hit reload + +Notes from past calls are all on GitHub [in this directory](https://github.com/WICG/turtledove/tree/main/meetings). + + +# Next video-call meeting: Wednesday Dec 6, 2023 + + +## Attendees: please sign yourself in! + + + +1. Michael Kleber (Google Privacy Sandbox) +2. Brian May (dstillery) +3. Itay sharfi (Google Privacy Sandbox) +4. Tim Hsieh (Google Ad Manager) +5. Lixing Dong (Google Ad Manager) +6. Russ Hamilton (Google Privacy Sandbox) +7. Orr Bernstein (Google Privacy Sandbox) +8. Paul Jensen (Google Privacy Sandbox) +9. Matt Menke (Google Chrome) +10. Nick Llerandi (Triplelift) +11. Kevin Lee (Google Privacy Sandbox) +12. Youssef Bourouphael (Google Privacy Sandbox) +13. Joel Meyer (OpenX) +14. Laurentiu Badea (OpenX) +15. Brian Schmidt (OpenX) +16. Isaac Foster (MSFT Ads) +17. Roni Gordon (Index Exchange) +18. David Dabbs (Epsilon) +19. Harshad Mane (PubMatic) +20. Sid Sahoo (Google Privacy Sandbox) +21. Caleb Raitto (Google Chrome) +22. Garrett McGrath (Magnite) +23. Abishai Gray (Google Chrome) +24. Marco Lugo (NextRoll) +25. Tamara Yaeger (BidSwitch) +26. Phil Lee (Google Privacy Sandbox) +27. Matt Davies (Bidswitch) +28. Stan Belov (Google Ads) +29. Jeff Wieland (Magnite) +30. David Tam (Relay42) + + +## Note taker: Isaac Foster + + +# Agenda + + +## Process reminder: Join WICG + +If you want to participate in the call, please make sure you join the WICG: https://www.w3.org/community/wicg/ + + +## Suggest agenda items here: + + + +* Isaac: + * Sec-cookie-label for no label: https://github.com/GoogleChromeLabs/privacy-sandbox-dev-support/issues/153 + * Buyer/Seller Reporting Questions: https://github.com/WICG/turtledove/issues/682#issuecomment-1710965068 + * Multi Tag Support via “Mixed Ranking”: (really, this + multi tag + bit leak discussi loop on and how we can be creative) https://github.com/WICG/turtledove/issues/846 + * Optional decouple bidding/reporting function urls to allow smaller k tuple: https://github.com/WICG/turtledove/issues/679#issuecomment-1703973736 +* Roni Gordon + * Sensitive signals - https://github.com/WICG/turtledove/issues/824 +* Tim Hsieh + * Seller Rendering Server (for native and video) - https://github.com/WICG/turtledove/issues/265 +* Itay sharfi + * New WICG to focus on trusted servers. Starting Dec 13 + * See: https://github.com/WICG/protected-auction-services-discussion/issues/27 +* Harshad Mane + * https://github.com/WICG/turtledove/issues/937 Need of a debug tool in Chrome for Protected Audience like Professor Prebid Chrome Extension developed by Prebid community +* David Dabbs + * Non-official chrome [testing labels](https://developers.google.com/privacy-sandbox/setup/web/chrome-facilitated-testing) seen in the wild (e.g. “preperiod”). Are these somehow related to this Chrome code (thanks to Przemysław Iwańczak for this src reference)**:** \ +https://github.com/chromium/chromium/blob/c0fcffe3bbcf87e4946dae228bd62d740568a429/components/safe\_browsing/core/browser/user\_population.cc#L91 \ +`bool is_preperiod = group.find("Preperiod") != std::string::npos;` + + +# Notes + +Isaac = note taker + +MK: Itay, new WICG for trusted servers + +https://github.com/WICG/protected-auction-services-discussion/issues + + +## New WICG meeting to focus on trusted servers. Starting Dec 13 + +Itay: Hi! Starting the WICG group for TEE based, December 13th at 9 AM PST/Noon EST, github and other things are there. + +Cross Talk: verify TZ, it’s the hour right after this one but bi-weekly currently + +DD: new WICG meeting, does that mean new repo and such + +Itay: yes + +DD: cool, I’ll start watching + +MK: calendar invite, good question, hmm…twixy, need to get chairs to do that, problematic from admin perspective + +BMay: Charlie (Harrison) did something kewl (for ARA), ask him about that. + +MK: would be great I’ll ask. + +Phillip Lee: I’m working on this new group, I’ll be sending out stuff, copying as much process as possible from here. + +MK: issue of where this will all be, let’s put a pointer into issue 88 (main this thing issue) so these folks can easily follow. + +Full logistics details, added after the fact: https://github.com/WICG/protected-auction-services-discussion/issues/27 + +MK: let’s move on, Tim, issue 265 + + +## Tim Hsieh: Seller Rendering Server (for native and video) - https://github.com/WICG/turtledove/issues/265 + +Tim (TH): Currently Protected Audience API supports HTML ads. The question is how do we support native and videos? Challenge is that the publisher or the seller needs to provide additional code within the rendering frame. One proposal is to use a Seller Rendering Server. The renderUrl consists of both Seller Rendering Server as the base URL and buyer ad asset server URL as a query parameter. During rendering, the browser calls the Seller Rendering Server. Seller Rendering Server calls buyers ad asset server to fetch ad assets. Seller Rendering Server could construct the final ad asset. This could be done today if the buyer constructs the renderUrl by combining Seller Rendering Server with Buyer Ad Asset Server. The request to Chrome is have Chrome combine the renderUrl. + +MK: Should have done this with Shivani around. I will give some initial reactions to the question of “how can we get the buyer and seller involved in the rendering process instead of just the buyer”. Need right privacy properties, key part of way PAA works is stuff inside of rendered creative shouldn’t know users identity, not even on the page being rendered, that needs preservation, so how can we let the seller influence it with the browser still being sure the “seller contribution to the rendering” doesn’t include user info. + +TH: yes makes sense, seller contribution shouldn’t contain user ID or some identifier, what I’m trying to understand is what the concern is if the seller can provide arbitrary code? + +MK: yes we have K-Anon protection, that is relatively weak, we’d actually like to provide the user with stronger protection in the future than we have today, today with rendering in iframes we do allow a join, which does allow an over time profile-building, fingerprinting risk, etc…FencedFrame approach will rid us of that issue…Event Level Reporting is the other vector of leaks for joins, that is only available to parties who attested, we’d need to extend the attestation to FencedFrames and really any provider of a pixel in the thing rendered, we’d increase the number of parties involved and attesting, what happens if one party is not attested, etc. So, for all those reasons, do want to solve the “cooperation issue” for rendering, but need to do so w/o letting arbitrary info flow from “more private” to “less private” via rendering. + +TH: I need to digest. + +Isaac Foster: Need to reread proposal, but is the only thing you're aksing for being able to send the buyer's renderURL to the seller's rendering server, or are you asking for more freedom of information than that, like info from seller context? + +TH: There are two proposals. In one, as the seller we provide a seller render URL, and the buyer provides their own, and Chrome combines the two into a single render URL that must be k-anon. + +Isaac: So today in Native there are more degrees of freedom, but in Video we have a video asset win, achieves k-anonymity, then the seller has an endpoint specific to that creative? or seller-endpoint/asset=buyer\_render\_server.html that then decorates it? or unique per asset? + +TH: Seller would be providing seller.ssp.com/render\_video and buyer provides buyer.com/video1, and chrome combines the two into one URL. URL provided by the seller should be common across all videos. + +Isaac: Seller auction config has a single render URL, Chrome would recognize that it's not just doing k-anon on each buyer URL but rather on the concatenation of the two strings, and then the SSP would be able to download and decorate, and get no new information outside the buyer URL? + +TH: I don't think there would be any macros. Buyer provides one URL per ad, combination by Chrome. + +Harshad: Seller would create a template of overall layout — so would the seller need different code for every publisher? + +TH: If the ad slot is native, then the URL to Seller Rendering Server would contain a template ID. + +MK: I just heard two different things. In response to Isaac I heard “seller has one renderURL for all of native one for video”, but in response to Harshad I heard “different per publisher site”, which is way different from a K-anon perspective…just to be clear, passing the K-anon threshold on every pub site that it has to render on seems way worse K-wise. + +TH: When I was responding to Isaac, I was responding to the video. In the video case, there’d be only one (or a small number) of Seller Rendering Server URLs for videos. In the native case, there could be one for each template. I agree that it would be harder clear K threshold. + +MK: native is the more interesting one here, K may not be the right tool here given the per-pub needs. The essence of what we need is some way to know that the seller contribution is not user specific but site specific. + +BMay: K requirement + seller side component will, by design, be a detriment to any small sellers. We should exclude sell side K components. + +Roni: what about the cases where the seller isn’t rendering, for instance video (out stream?).(Adding link to Index’s PBJS docs re: [outstrream video](https://docs.prebid.org/dev-docs/bidders/ix.html#indexs-outstream-video-player) after discussion for reference with publisher’s rendering capabilities) + +MK: yes agree need to figure that out, multi SSP stuff + +Roni: native is different, video has lots of flexibility + +Paul Jensen: agree with separating out native and video, very different needs. Fenced Frame read only mode does allow for some of what native wants, believe the way it works is it loads stuff from network related to creative, and then the FF shifts into read only mode where it can read info but no more network requests are allowed. + +MK: oh yeah I like that, but concern that something “font loading” won’t work. + +Paul/MK Cross Talk: might work, well maybe, eh I worry and don’t feel safe + +PJ: not sure exactly how this works + +MK: something something shared storage, could extend that. + +Stan Belov: what’s the risk you’re seeing exactly? + +MK: fingerprinting risk; + +SB: isn’t seller attested? + +MK: yes, but once information is in the rendering URL, it's not just the seller that sees it, it's every server involved in rendering in any way, even a single pixel inside the creative. + +SB: isn’t that true for buyers too? + +MK: only matters if you’re combining info across contexts. + +Matt Menke: if auction config had a seller URL, could load that seller URL in the FF, after network access cut off pull down the buyer URL, then you don’t have access to x-site and network at same time. + +MK: interesting, need to chew. + +PJ: yeah this gets tricky, think fonts, lots of stuff to load. + +MK: jah interesting, must to think. + +Lixing, work with Matt/Tim: what Matt proposed is an option…some sub-optimalities there + +TH: two options proposed, one is seller rendering server, one is more client side, maybe seller provides generateHTML option, would that client side solution allow the mixing? + +MK: my read is it would allow that… + +TH: I will think more + +BMay: clarify, are there two URLs and network calls, one from sell one from buy? + +MK: yes + +BMay: so back channel with timestamps. + +MK: for sure. + +Cross Talk: timing attacks are hard to deal with, maybe we just don’t allow it + +BMay: Preloading assets is very expensive, suggesting that limiting ourselves to a single call as much as possible would be best. + +Isaac: Regarding rendering and fonts and native, two questions. (1) Where does variation show up in Native? Is it just based on top-level site and styling variation? can it be limited to some kind of browser signal where the browser has more control over what gets passed through? (2) On fonts, I wonder if the user agent having a bit of advertising layer on top of what it already does could pre-fetch some sorts of common things needed for rendering + +MK: I haven’t thought through the full set of risks. Needs more thought. Needs more design work on the privacy side and coordination side between multiple SSPs. . + +Tims issue, 265, been around a while, let’s keep working on this there, needs more design, two solutions are presented in a lot of detail, might need to take a step back from those implementation and think at a larger scope, security, data flows, etc, but it’s a good place to start and ideate. + +MK: 5 minutes left + +Isaac: The Chrome label for no-group. Is this the right place to talk about that? Namely https://github.com/GoogleChromeLabs/privacy-sandbox-dev-support/issues/153 + +MK: Right people not in the room. Initial set of population labels did not include a none-of-the-above. Maybe can be added later. + +PJ: I talked to Josh about this and will push him to answer on bug issue. + +MK: See you Dec 13th. + +BM: End of year meetings? + +MK: Will we meet Dec 20th? We should not meet on 27th. Tentatively will meet Dec 20th. Note that protected server meeting will happen after this meeting next week, the 13th. diff --git a/meetings/2023-12-13-FLEDGE-call-minutes.md b/meetings/2023-12-13-FLEDGE-call-minutes.md new file mode 100644 index 000000000..a07a19cc6 --- /dev/null +++ b/meetings/2023-12-13-FLEDGE-call-minutes.md @@ -0,0 +1,269 @@ +# Protected Audience WICG Calls: Agenda & Notes + +Calls take place on most Wednesdays, at 11am US Eastern time; check [#88](https://github.com/WICG/turtledove/issues/88) for exceptions. + +That's 8am California = 5pm Paris time = 4pm UTC (during winter) + +This notes doc will be editable during the meeting — if you can only comment, hit reload + +Notes from past calls are all on GitHub [in this directory](https://github.com/WICG/turtledove/tree/main/meetings). + + +# Next video-call meeting: Wednesday Dec 13, 2023 + +To be added to a Google Calendar invitation for this meeting, join the Google Group https://groups.google.com/a/chromium.org/g/protected-audience-api-meetings/ + + +## Attendees: please sign yourself in! + + + +1. Michael Kleber (Google Privacy Sandbox) +2. Shankar Venkataraman (Jivox) +3. Brian May (dstillery) +4. Amit Gupta (Jivox) +5. Taranjit Singh (Jivox) +6. Antoine Niek (Optable) +7. Xavier Capaldi (Optable) +8. Roni Gordon (Index Exchange) +9. David Dabbs (Epsilon) +10. Harshad Mane (PubMatic) +11. Phil Lee (Google Privacy Sandbox) +12. Orr Bernstein (Google Privacy Sandbox) +13. Garrett McGrath (Magnite) +14. Caleb Raitto (Google Chrome) +15. Paul Jensen (Google Chrome) +16. Shankar Venkataraman (Jivox) +17. Isaac Foster (MSFT Ads) +18. Alex Cone (Google Privacy Sandbox) +19. Tim Hsieh (Google Ad Manager) +20. Stan Belov (Google Ad Manager) +21. David Tam (Relay42) +22. Fabian Höring (Criteo) +23. Youssef Bourouphael (Google Privacy Sandbox) +24. Sid Sahoo (Google Chrome) +25. Matt Menke (Google Chrome) +26. Alonso Velasquez (Google Chrome) +27. Jonasz Pamuła (RTB House) +28. Zach Edwards (Victory Medium) +29. Miguel Morales (Tech Lab) +30. Jacob Goldman (Google Ad Manager) +31. Matt Davies (Bidswitch (Criteo)) +32. Andrew Pascoe (NextRoll) +33. Abishai Gray (Google Chrome) +34. Shivani Sharma (Google Chrome) +35. Daniel Rojas (Google Chrome) + + +## Note taker: Orr Bernstein + + +# Agenda + + +## Process reminder: Join WICG + +If you want to participate in the call, please make sure you join the WICG: https://www.w3.org/community/wicg/ + + +## Suggest agenda items here: + + + +* [General Announcement] Jeroune + * The Google Privacy Sandbox team will be hosting our next series of webinars on the Protected Audience API. This set of webinars will focus on reporting and how you can measure data related to a Protected Audience auction. The first **Americas friendly session** is happening on** Jan. 16th 3-4 pm ET**. A second **EMEA friendly session** is happening **Jan. 18th 12-1 pm GMT**. A third **Japanese language session** will be held on **Jan 30th 9-11 am JST**. To join, please register below: + * AMER-friendly: [Register Here](https://rsvp.withgoogle.com/events/protected-audience-webinar-3-reporting-amer) + * EMEA-friendly: [Register Here](https://rsvp.withgoogle.com/events/protected-audience-webinar-3-reporting-emea) + * Japanese language: [Register Here](https://rsvp.withgoogle.com/events/protected-audience-office-hour-3) +* Isaac: + * Buyer/Seller Reporting Questions: https://github.com/WICG/turtledove/issues/682#issuecomment-1710965068 + * Persistent Opt Outs, Maybe CHOPS - https://github.com/WICG/turtledove/issues/915 + * Multi Tag Support via “Mixed Ranking”: (really, this + multi tag + bit leak discussion and how we can be creative) https://github.com/WICG/turtledove/issues/846 + * Optional decouple bidding/reporting function urls to allow smaller k tuple: https://github.com/WICG/turtledove/issues/679#issuecomment-1703973736 +* Harshad Mane + * https://github.com/WICG/turtledove/issues/937 Need of a debug tool in Chrome for Protected Audience like Professor Prebid Chrome Extension developed by Prebid community +* David Dabbs + * Non-official chrome [testing labels](https://developers.google.com/privacy-sandbox/setup/web/chrome-facilitated-testing) seen in the wild (e.g. “preperiod”). Are these somehow related to this Chrome code (thanks to Przemysław Iwańczak for this src reference)**:** \ +https://github.com/chromium/chromium/blob/c0fcffe3bbcf87e4946dae228bd62d740568a429/components/safe\_browsing/core/browser/user\_population.cc#L91 \ +`bool is_preperiod = group.find("Preperiod") != std::string::npos;` +* Shankar Venkataraman + * Interest group ownership construct missing for Third Party Ad servers whitelisted by Advertiser ([#924](https://github.com/WICG/turtledove/issues/924)). We will discuss and explain the model that we would like supported in the context of Protected Audiences. +* Xavier Capaldi + * Status on support for multi-level frequency capping (using prevWinsMs beyond a single interest group) https://github.com/WICG/turtledove/issues/138 +* Tim Hsieh + * Follow up discussion on Native support for FLEDGE https://github.com/WICG/turtledove/issues/265#issuecomment-1823582905. + * We would like to propose an alternative option using web bundle that could potentially address both signal mixing and k-anonymity issue +* Itay + * Announcement: first Protected Auction Services meeting in an hour + * + * https://github.com/WICG/protected-auction-services-discussion +* Jonasz (RTB House) + * 3pc deprecation timeline: https://github.com/WICG/turtledove/issues/717#issuecomment-1847118918 +* Roni Gordon + * Sensitive signals - https://github.com/WICG/turtledove/issues/824 +* Matt Davies (Bidswitch) + * Origin / Traffic shaping - https://github.com/WICG/turtledove/issues/951 + + +# Notes + +## https://github.com/WICG/turtledove/issues/937 Need of a debug tool in Chrome for Protected Audience like Professor Prebid Chrome Extension developed by Prebid community + + + +* Harshad Mane + * This issue is about having debugging in Chrome like what’s available in Professor Prebid + * Professor Prebid is a Chrome extension developed by the community that helps to show which bidders are configured for which ad slots + * Already, all of the configuration that goes into the auction can be seen here. + * Would like to have something similar for Protected Audience as well. + * Someone who’s debugging would need to know what’s going on. + * For Professor Prebid, it uses information accessible to anyone. However, Protected Audience is a closed system. +* Michael Kleber + * Is your goal to have a separate extension, or integrating into the existing Professor Prebid extension? +* Harshad + * Would like to see a new extension. There’s a lot of complexity already in the Professor Prebid extension. +* Kleber + * Some tradeoffs. Professor Prebid already has a lot of the detail of how auctions work. +* David Dabbs + * There’s a middle ground available. +* Paul Jensen + * There’s a page that has information on Protected audience debugging on the web using DevTools: https://developers.google.com/privacy-sandbox/relevance/protected-audience-api/troubleshoot +* Harshad + * The problem I’ve seen is that it’s not obvious from these tools which ad slot the auction is happening. +* Paul + * That’s true. We could extend the information in DevTools to show more information about which slot and auction everything is associated with. The information in DevTools is likely accessible to an extension. +* Kleber + * Is your goal to have DevTools extended to satisfy more of these needs, or to support a community-driven solution? +* Harshad + * Would prefer a community-driven solution. +* Paul + * One way we can support this is to improve the information we show via DevTools, for example, we could show more information about the call to runAdAuction. +* Kleber + * One thing we can do is to Identify what we should add as information exposed in DevTools to support a community-driven solution. +* Harshad + * Is there any concern from Chrome on showing more information in DevTools? +* Kleber + * The Chrome philosophy is that as much as possible should be seen in DevTools. You should always be able to see what’s going on in your own machine. +* Harshad + * Is there someone on Chrome team that can help us to proceed? +* Paul + * We’ll find someone who knows the intersection of DevTools and Chrome Extensions. +* Brian May + * Was going to suggest that, if Chrome could put together an extension that showed what’s happening in the auction, then someone in the Prebid community could use it as a reference implementation +* David + * I heard some targeted set of things Chrome can do - low hanging fruit - e.g. key things in the timeline +* Paul + * Yes, adding runAdAuction calls to the timeline, attributing other events to the different auctions. + +## Non-official chrome [testing labels](https://developers.google.com/privacy-sandbox/setup/web/chrome-facilitated-testing) seen in the wild (e.g. “preperiod”). Are these somehow related to this Chrome code (thanks to Przemysław Iwańczak for this src reference): +https://github.com/chromium/chromium/blob/c0fcffe3bbcf87e4946dae228bd62d740568a429/components/safe\_browsing/core/browser/user\_population.cc#L91 +bool is_preperiod = group.find("Preperiod") != std::string::npos; + + + +* David Dabbs + * Is this something that it’s possible to be produced by Chrome? We’re receiving these on our DSP platform from some upstream partner. Trying to determine the provenance. +* Sid Sahoo + * This is produced by Chrome. It’s for the experiment that’s coming up for Q1/Q2, selection of the population needs to be random. When we say it’s 5% of traffic, it’s not an exact science as well. It’s adjusted, you then see if you’re overshooting/undershooting, and adjusted to get closer to 5%. +* David + * So, anything that’s labeled with preperiod, are those going to be in the actual groups? Or is just checking that your dice rolling is correct. +* Sid + * Not necessarily. Some of these may be in different groups. +* David + * So, it’s helpful to add warnings to the documentation warning people not to rely on these. +* Kleber + * Yes, we should add this to the experimentation guide. You should treat these undocumented labels the same as you would any other unlabelled traffic. +* Brian + * Will Chrome publish something about how it’s picking these populations? +* Kleber + * The populations that are being used in the Chrome-facilitated testing are using the standard Chrome randomly assigned testing infrastructure. Every browser has a random seed between 1 and 4000 on it. This 4000 buckets is the way Finch trials work in general. Trying to find good buckets to use to make sure that we’re not using ones that are imbalanced in terms of size. It’s the same infra we use for all of our A/B tests. +* Shankar + * Is the trial, when you do this kind of sampling, do you need the advertiser to participate in these trials? +* Kleber + * When Chrome creates the random sample, it’s before any of these other things happen. When Chrome creates the label, it’s just 1.5% of all the browsers in the world. The API is available for everybody; 1.5% will have this label attached to them. People are welcome to test on whatever traffic they want. Advertisers are welcome to use the PAAPI to create audiences on any traffic they want. These labels indicate a population that’s a good sample to test on. Having a small slice of traffic labeled might make your testing useless e.g. if your audience is particularly small. +* Shankar +* Kleber + * There are a few slices of traffic that GAM has said that they will always run Protected Audience auctions - the mode B slices and one of the five mode A slices. +* Sid Sahoo (in chat) + * https://support.google.com/admanager/answer/13178817 +* Kleber + * This shows which slices of traffic GAM is going to use PAAPI always. In other traffic, GAM does what everyone else is doing, potentially using the PAAPI, but not necessarily. +* Roni (in chat) + * ah, I remember my question -- there is a "fake" prefix that is also sent from "forcing" labels via experiment flags -- that, too, isn't documented +* Roni + * There are other labels there too from the experimental flags, which add a "fake" prefix. Can the documentation be exhaustive? +* David Dabbs (in chat) + * Sid Sahoo I will ask this question in the chat: are the label experiments listed as "client variations?" +* Kleber + * Any labels that you see that are not listed, don’t rely on them. You can go digging through source code. But like Sid said, we should definitely update the documentation to make some progress in that direction. + +## Status on support for multi-level frequency capping (using prevWinsMs beyond a single interest group) https://github.com/WICG/turtledove/issues/138 + + + +* Xavier Capaldi + * Reference a discussion in the thread from 2021, we need a frequency capping that’s beyond a single interest group. If we have IGs scoped to campaigns, we can’t frequency cap. Has there been more research on this? +* Kleber + * Are you talking about a person joined into multiple IGs, which were joined from the same site? Or joined from two different sites and want to frequency cap across those? +* Xavier + * Two different sites. +* Kleber + * You’re right that’s not possible right now. Making that possible is not in line with how we’re envisioning privacy within the PAAPI. Every IG should have information coming only from information coming from a single site. If its bidding behavior is affected by information about a different site, then a clever person could use prevWins from other IGs to build a cross-site profile, and learn about all the websites you’ve been on. It’s true that we don’t completely lock down information sharing, e.g. the presence of prevWins at all does some accumulation of information, so it’s not rock solid right now. But this would create more cross-site information at IG auction time. + * However, there is a concept of negative targeting, this is a feature we just added to the Protected Audience API recently, after some discussion in this forum. It’s a way for user behavior on one site to filter out contextually-targeted ads on another site. There’s an open issue (filed by Critero, I believe?) that’s asking about the possibility of using negative targeting for bids that came from IGs joined from a different site. +* Alonso (in chat) + * https://github.com/WICG/turtledove/issues/896 +* Kleber + * Might gesture in the direction of what you’re alluding to. If this would be helpful to you, please chime in on the issue. +* Alonso + * https://github.com/WICG/turtledove/issues/319 (negative targeting against contextual ads) is already implemented. https://github.com/WICG/turtledove/issues/896 is the the idea to use negative targeting against positive IGs. +* Xavier + * I’ll take a look at them, thank you. + +## Announcement: first Protected Auction Services meeting in an hour + +**Protected Auction Services discussion - notes** + +**https://github.com/WICG/protected-auction-services-discussion** + +* Itay Sharfi + * In 15 minutes, we are starting, for the first time, the meeting about trusted servers. If you’re interested, add your topic to the agenda and you’re welcome to join. Phil and I are both organizing the meeting for now. +* Kleber + * I hope people will join that meeting. I have a conflict, but Paul will be joining. + +## Follow up discussion on Native support for FLEDGE https://github.com/WICG/turtledove/issues/265#issuecomment-1823582905. + +**We would like to propose an alternative option using web bundle that could potentially address both signal mixing and k-anonymity issue** + +* Tim Hsieh + * The idea is that the seller will provide a generateHTML function. As part of the HTTP response header, they can provide the WebBundle that contains any asset as referenced from the HTML. + * The buyer will provide a native asset as JSON in the header. They can provide the WebBundle that contains any asset as referenced from the HTML. + * No network access from the rendering frame. Hopefully, fewer requests that contain both contextual information from seller and rendering information from the buyer. + * Extension of the second proposal. Going to flesh it out. An overview of that idea. +* Kleber + * Let me set the stage by comparing this to what we talked about last week. + * Your goal is that it should be possible for buyer and seller to combine inside the creative. + * The reason we were nervous about this last week is that it’s an opportunity to exfiltrate combined information to the network. Ads rendered in the way you're proposing would involve information from IG and information from surrounding publisher page to combine. + * The thing that you're proposing which makes it OK to combine these is that the FenceFrame has relinquished its access to the network. The problem we got stuck on last week was the resources needed for rendering — for example, what if there’s a font that’s needed, and it’s not yet available? + * Your new answer, which we didn't think of last week, is that all of those resources could be made available in a WebBundle. Since that web bundle is provided along with the ad/IG, we can load it before information from the publisher page and the IG combine, before we need to give up network access. +* Shivani (the primary engineer who’s been thinking about rendering in FencedFrames) + * What happens before FencedFrame renders - considering the input part, the buyer needs to provide some information back to the publisher. Anything that’s returned back from runAdAuction needs to be opaque. Need to make sure that we’re still satisfying that guarantee. +* Tim + * That’s correct. The native asset will be provided as an argument for generateHTML. It’ll be running inside Chrome, in a worklet that has no network access. +* Shivani + * In terms of a FencedFrame that doesn’t have network access, what happens when navigation needs to happen from that ad, and reporting? +* Tim + * Good point. For reporting, FencedFrameReporting API. For top-frame navigation, we need some way for buyer or seller to declare, here’s the set of URLs to which we can be navigating to. Haven’t thought through that yet. +* Shivani + * Might need to consider pre-declared navigation URL. With reporting, it would be possible to exfiltrate the combined information of seller and buyer. Would need aggregate reporting. +* Shankar + * This is the protocol currently in place for video ads. The seller renders the creatives. In the context of Fenced Frames, it will be an allowlisted set of URLs that you’ll be able to tie back to the origin. The buyer wins the auction, sends the dummy asset, and the seller renders it. +* Kleber + * Just to recap from last week, the fundamental issue is that if the seller-provider information is universal - not based on the publisher page or the URL - easily accommodated today. Do the combination in advance. Lots of ways that combination can happen. But if the information provided by the SSP is provided at the moment of the auction, if the information about the user is flowing into the rendering frame, it’s on the other side of the privacy guarantee we’re trying to provide. Either that information about the user does not combine, or that if it does combine, that it can’t be exfiltrated. +* Shivani + * Have a capability coming up that’s in the Fenced Frame repositories, either that the FencedFrame is created with no network access, or that it starts with network access, and then it renounces network access and in exchange can access information from SharedStorage. +* Kleber + * What Tim is suggesting is that the FencedFrame relinquishes its network access, and in exchange can access a WebBundle +* Roni + * Also needs to work in an iframe, and we have the same problem today. Video XML in iframes before 2026. +* Kleber + * What we’re talking about is easy to do today if you have code on the publisher page and can postMessage into the iframe, but if you don’t have code on the publisher page (like you are a component-seller SSP), then this is still an open issue. diff --git a/meetings/2023-12-20-FLEDGE-call-minutes.md b/meetings/2023-12-20-FLEDGE-call-minutes.md new file mode 100644 index 000000000..b7dae4792 --- /dev/null +++ b/meetings/2023-12-20-FLEDGE-call-minutes.md @@ -0,0 +1,239 @@ + +# Protected Audience WICG Calls: Agenda & Notes + +Calls take place on most Wednesdays, at 11am US Eastern time; check [#88](https://github.com/WICG/turtledove/issues/88) for exceptions. + +That's 8am California = 5pm Paris time = 4pm UTC (during winter) + +This notes doc will be editable during the meeting — if you can only comment, hit reload + +Notes from past calls are all on GitHub [in this directory](https://github.com/WICG/turtledove/tree/main/meetings). + + +# Next video-call meeting: Wednesday Dec 20, 2023 + +To be added to a Google Calendar invitation for this meeting, join the Google Group https://groups.google.com/a/chromium.org/g/protected-audience-api-meetings/ + + +## Attendees: please sign yourself in! + + + +1. Paul Jensen (Google Chrome) +2. Brian May (dstillery) +3. Wojciech Biały (Wirtualna Polska Media) +4. Amit Gupta (Jivox) +5. Shankar Venkataraman (Jivox) +6. Roni Gordon (Index Exchange) +7. Szymon Gajda (Wirtualna Polska Media) +8. Isaac Foster (MSFT Ads (there in 5)) +9. Laurentiu Badea (OpenX) +10. Youssef Bourouphael (Google Chrome) +11. Orr Bernstein (Google Privacy Sandbox) +12. Harshad Mane (PubMatic) +13. Sid Sahoo (Google Chrome) +14. Owen Ridolfi (Mediaocean) +15. Antoine Niek (Optable) +16. Caleb Raitto (Google Chrome) +17. David Dabbs (Epsilon) +18. Ricardo Bentin (Media.net) +19. McLeod Sims (Media.net) +20. Brian Schmidt (OpenX) +21. Fabian Höring (Criteo) +22. Chris Nachmias (Mediaocean) +23. Tamara Yaeger (BidSwitch) +24. Anthony Yam (Mediaocean/Flashtalking) +25. Jonasz Pamuła (RTB House) +26. Jeroune Rhodes (Google Privacy Sandbox) +27. Drew Schoentrup (Big Crunch) +28. Maciek Zdanowicz (RTB House) +29. Nick Llerandi (Triplelift) +30. David Tam (Relay42) +31. Becky Hatley (Mediaocean/Flashtalking) +32. Stan Belov (Google Ads) +33. Abishai Gray (Google Chrome) +34. Matt Davies (Criteo | Bidswitch) +35. Alex Peckham (Mediaocean/Flashtalking) + + +## Note taker: <please volunteer> + + +# Agenda + + +## Process reminder: Join WICG + +If you want to participate in the call, please make sure you join the WICG: https://www.w3.org/community/wicg/ + + +## Suggest agenda items here: + + + +* [General Announcement] Jeroune + * The Google Privacy Sandbox team will be hosting our next series of webinars on the Protected Audience API. This set of webinars will focus on reporting and how you can measure data related to a Protected Audience auction. The first **Americas friendly session** is happening on** Jan. 16th 3-4 pm ET**. A second **EMEA friendly session** is happening **Jan. 18th 12-1 pm GMT**. A third **Japanese language session** will be held on **Jan 30th 9-11 am JST**. To join, please register below: + * AMER-friendly: [Register Here](https://rsvp.withgoogle.com/events/protected-audience-webinar-3-reporting-amer) + * EMEA-friendly: [Register Here](https://rsvp.withgoogle.com/events/protected-audience-webinar-3-reporting-emea) + * Japanese language: [Register Here](https://rsvp.withgoogle.com/events/protected-audience-office-hour-3) +* Isaac: + * Buyer/Seller Reporting Questions: https://github.com/WICG/turtledove/issues/682#issuecomment-1710965068 + * Persistent Opt Outs, Maybe CHOPS - https://github.com/WICG/turtledove/issues/915 + * Multi Tag Support via “Mixed Ranking”: (really, this + multi tag + bit leak discussion and how we can be creative) https://github.com/WICG/turtledove/issues/846 + * Optional decouple bidding/reporting function urls to allow smaller k tuple: https://github.com/WICG/turtledove/issues/679#issuecomment-1703973736 +* Shankar Venkataraman + * Interest group ownership construct missing for Third Party Ad servers whitelisted by Advertiser ([#924](https://github.com/WICG/turtledove/issues/924)). We will discuss and explain the model that we would like supported in the context of Protected Audiences. +* Jonasz (RTB House) + * 3pc deprecation timeline: https://github.com/WICG/turtledove/issues/717#issuecomment-1847118918 +* Roni Gordon + * Sensitive signals - https://github.com/WICG/turtledove/issues/824 +* Matt Davies (Bidswitch) + * Origin / Traffic shaping - https://github.com/WICG/turtledove/issues/951 + + +# Notes + +Paul filling in for Michael. + +Paul (Chrome): Jeroune has announcement + +Jeroune (Sandbox): Sandbox team hosting webinars on PA on reporting and measuring data, including auction, user engagement, attribution data from conversion. Links will be posted. Next sesh is Jan 16th, EMEA Jan 18th, Japan Jan 30th. + +Paul: Isaac’s issues + +## Persistent Opt Outs, Maybe CHOPS - https://github.com/WICG/turtledove/issues/915 + +Isaac (MSFT): Potential k anonymity issue between creative URL and reporting, but today we have a need (legal type) to provide privacy center. Xandr has one, MSFT has one too, but we’re not merged yet. A user will be able to go to our privacy ctr and wish to opt out of all Xandr-based advertising. We do get couple diff flavors of that, the idea is to not show ads from our platform. How we do that today is to opt out their cookie, and we will in some cases record it to expand to graph-type deal. I think that’s a challenge in partitioned world. We can opt out of ads in our privacy ctr, but not every website they go to, can’t get it shipped across partitions. Interesting challenge. Someone mentioned GPC but if there’s someone who can explain I didn’t quite get it. If there was a way for browser to expose API for enumerated list of flags to be sent to domain. In case of opt-out it could be helpful. + +Shankar (): TCF consent string will tell us if we can sell ads or not to specific user. + +Isaac: How would the TCF string be shared across partitions? + + +Shankar (Jivox): Should come in w ad call after auction is won from publisher. Ultimately user is sitting on pub site. Then coming to Xandr site… + +Isaac: We’re talking about diff cases. In case of user vising NYtimes. Com… popup asks what to do w cookies. I say reject everything. I’m referring to case where someone goes to Xandr privacy ctr and asks to not see ads from our platform on on any partition. + +Shankar: That model is gone, as long as pub is sending right consent and they’re not targeting someone who has not given consent, why bother? + + +David D (Epsilon): In today’s world we have to have all these industry user portals or our own way to opt out because there isn’t a global BRS (big red switch). In this new mechanism there is a way for user to control this and not worry about individual companies they can turn off ads targeting. To point of wanting control, to Shankar’s point, if in jurisdiction where there is site-based consent, you can get that signal, and if you buy into whole GPC thing that will be available to you in browser signals. Not sure something will exist as global buyer-seller. + +Shankar: Even if on site level, like happens in Germany, I would say right model is to have something for browser to….. at point of selling ad, it’s the only way to secure. + +Roni (Index): There’s always a way to have global 3P cookie opt-out (https://optout.networkadvertising.org/). I hear question whether we need such a facility if not based on cookie. If you read through the lines it’s interest based advertising. The question for Chrome is if we can’t put it in 3P cookie, where to put it? + +Paul: Chrome does offer ways to turn off targeting mechanisms. In settings you can turn off PA or other parts of Sandbox. Any sort of middle ground with less-global, this is a space that ppl tried to explore a few times and failed a few times. They devolve into fingerprint mechanism that we haven’t thought about when creating solution, having to go w more global solution in the end. I don’t know if there is a great middle ground. + +Shankar: What is proposed is that pub stuffs consent into \_\_\_ storage which is available for all white listed advertisers and DSPs. At this point everybody can read if the consent is there. + +Brian (Distillery): I was going to suggest this as formal project, anyone is subject to receiving an opt-out request. Other parties in chain may or may not have access to add origin. We should look thru use cases to see who will get opted out of, rather than trying to quickly come up w solution. + +David D: Tracking of opt-out becoming fingerprinting vector, is it at all on the PA horizon to … every ad will be in a fenced frame. Is it on the horizon for browser to say it knows that PA ad is being rendered in this frame, have an affordance to some kind of transparency to the user. We know boatload of who was participating, including who won the auction. If there isn’t a concept of some opp to provide transparency for these frames facilitated to browser, then there’s not much to discuss. + +Paul: This is something we thought a bit about, haven’t chosen UI direction yet. In general browser knows evtg, including origin and interest group. Problem is some things are harder to communicate to user. The user was on an origin in last 30 days, so they probably have some familiarity of that origin. The opt-out Isaac was talking about, per adtech, user may not have familiarity w owner of interest group. Very hard thing for browser to convey to user. + +David D: They don’t today but we try to make it possible to understand that triangular thingy on top of ad. + +Isaac: I just want to make sure I’m being clear, the thing I care about is, looking at our privacy center, we don’t mention cookies anywhere. It’s referring not to cookies, it’s referring to sales / sharing of data. If the solution I called out is a terrible one it’s fine, the problem I’m more interested in – today our lawyers told us we must have way for user to have control over how data is processed / stored / shared. The way we have done that has relied on cross-partition, which is going away. Not a Jan 4th emergency, but could be real problem. To Brian’s point, may need to take elsewhere. Hypothetically if 3P cookies became partitioned everywhere always, and no opp to have them across anywhere, it would be interesting to see whether to exempt that. + +Harshad (PubMatic): Maybe can be made available in PA whether user has opted out. + +Paul: This is the middle ground that devolved into fingerprinting. We can uniquely identify people that way. + +Brian: This question is multi-dimensional. If you give users a list, they will opt out of all. The next level down is opting out of specific advertiser. How do I opt out of that? You don’t. You opt out of all. My point is we have to contend w data subject rights at some level w/in context of PAAPI and PSB. We’re going to have to deal w how to comm to user why they’re seeing what they’re seeing. I’ve been working w IAB group on how that gets communicated, but how does that slot into PAAPI universe? I don’t think we need to start a new group, but we need to focus on data rights. If we determine not to take on directly, still need to figure out how to support those who are. + +Paul: maybe we should encourage people to offer any solutions they have in Isaac’s issue. + +Brian: I suggest we make Isaac’s a part of larger data subject rights issue. + +Harshad: If only specific ad tech player knows about opt out, can there still be case of finger printing? + +Paul: Only if adtechs are not sharing lists they’re receiving. + +David D: Isaac, what did lawyers have to say about Safari cookies being in the dustbin? We should put this in as something to discuss, but unlike ad targeting, there is “big red switch” not on a granular level. It affords us some time to do someone more granular. In Maslow’s hierarchy of needs we need to focus on getting nose off tarmac to deliver on guarantees we have before all cookies are gone. + +Isaac: I did put a semi-answer, I can ask lawyers for their real legal opinion. + +Brian: Just want to add last note about doing opt-outs too quickly, damage of massive ppl opting out of system is difficult to recover from. Suggest to move into territory slowly. + +Paul: Shankar do you want to talk about interest group ownership? + +## Interest group ownership construct missing for Third Party Ad servers whitelisted by Advertiser ([#924](https://github.com/WICG/turtledove/issues/924)) + +Shankar: The way we operate in 3P cookie world is DSP wins, on PSB PA at point of registry, supposed to give not only bid logic but creative. This is done by DSP, usually creative comes much later. Even in current world people transfer then creative runs. Creative is ready probably weeks after \_\_\_ has been put in place. IN actual execution of model, no way for advertiser to have 2 diff DSPs. Unless I gave creative URLs to advertiser, they could have multi campaigns running on same interest group. Why is it that creative + +Paul: We built interest group creative system to be able to add creatives later. Meant to address problem of not having creatives. The other part is, interest groups are fairly flexible. We don’t dictate that the advertiser has to be owner of interest group. Almost anyone can create interest group and use them to bid. We also don’t require ad creative URL is same origin as anyone else. Whether it’s the DSP or advertiser that owns interest group, they can still delegate rendering of ad to third party entirely. + +Shankar: The second part is when the bid is won, will the ad get context of who won the bid? Bid logic is gained by somebody else. There are 2 providers in the ecosystem. How will URL know which interest group won (?) For every interest group, + +Paul: Any info that’s needed at ad rendering time can be put into URL. + +Shankar: We have macros that get passed to DSP. + +Paul: Not really any restriction on k anonymity. Pretty free-form. Could be specifying to ad server to render ad #4 for campaign #5. + +Shankar: For example, on e Commerce site, they have category and subcategory. I can create an interest group at product level and sub level… 4 interest groups for 1 visit. Do we have to find out bid logic for each interest group, ad calls, lots of data. + +Paul: We had a k anonymity restriction, but we dropped that designed a long time ago. You can create 1 interest group that has 4 diff ads. Then add or remove ads that target that. If you want you can put in as much 1P info from joining side. You can write user journey in interest group. Then if you want to start ad campaign you can push down to that interest group. + +The way k anonymity bootstraps itself, the hope is that people don’t have to keep fine tuning which ads are shows ot how many ppl. If your ads are broadly targeted it should fall into place and browser will worry about what to show. + +Shankar: I’ll update the ticket based on issue. + +Amit (Jivox): I think what Paul is suggesting, the only issue is adding multi ads in same interest group means we are targeting same user. Literally 1:1 is gone so we are saying our cohort is now based on interest group, which is same for category, sub category, and individual product. It has to be multi interest groups in that case? + +Paul: You can have 1 broad interest group, in there there is user bidding signals, where you can put your info including user flow. You don’t have to split across interest groups. + +Shankar: We have party that provides bid logic is different from party that \_\_\_\_ + +Paul: With k anonymity restriction we’re avoiding microtargeting. + +Shankar: I should be still be able to show the right ad based on product, but if I not + +Amit: What we are leaning towards is ad tech cohort to resolve this issue. We cannot use the same ad tag to target multi cohorts itself. + +Paul: What is an ad tag? + +Amit: The render URL, hits ad server directly. Bidding logic belongs to DSP. + +Shankar; Unless we do another round of bid logic to resolve. + +Paul: Is problem that you want more info at bid time or ad serving time? + +Shankar: Ad serving. We need to know which interest group won the bid and parameters around that. + +Paul: Anything you want can be put into ad created URL. + +Harshad: Shankar, why not putting two interest groups? + + +Shankar: That’s exactly what we have to do. For example, suppose red color sofa. Interest groups are created for furniture. + +Paul: You may want to break up your ad tags like that, not interest groups. Say user saw red sofa, say update time you send that person 3 ads, then at ad serving time you get the URL that says they are interested. + +David T (Relay42): We work w a lot of eCommerce companies w huge product catalogs. We’ve always been able to do that using campaign manager w Google studio. Google studio manages prod catalog, we provide prod ID to render specific asset w/in the HTML creative. To do this currently you have to create loads of interest groups. The question is how ot create one interest group where the users are placed in that group that actually see the right product. If you have to create loads of interest groups, it’s just not feasible. + +Shankar: Possibly feasible but not sure how it would work. We are trying to replicate what we are doing today, let us go back to spec to review. The other problem is that most eComm ads are in sequence. Multiple products show in sequence. + +Brain: This is going towards day of reckoning for PAAPI. In legacy model of advertising, big servers figure out what to bid for each user with. PAAPI will take all that intelligence and ship it down to browser or auction server. If you take all the intelligence serving ads across internet, the space will be overused very quickly. Paul said a couple times that you can put as much data as you want in the interest group. We need to be careful because there are limits on a browser. We need to figure out constraints. + +Paul: You don’t have to store all that info in browser, you can assign an identifier and put that in the interest group. Different from today’s model + +David T: You mentioned that ad components, are they URLs? Can those URLs feed into JPEG image or just HTML? + +Paul: Haven’t thought of it, guess they could be either one? + +David T: If it’s a bunch of assets, pics of diff products, how does the ad itself know what to pick? + +Shankar: We thought about to actually at point of sending HTML, could be JPEG. If you want to send JPEGS to browser, need to register worklet as part of interest group, worklet decides what to render. + +Paul: It could just be an HTML wrapper, at bidding time is when the products would be selected. + +Shankar: Unless you hard code it into URL. + +Paul: You might have overall fenced from that is carousel ad, and individual components would be some kind of headphones. + +Shankar: Our problem is with millions of URLs. + +Brian: Propose for Shankar to create formal discussion w slides diff --git a/meetings/2024-01-03-FLEDGE-call-minutes.md b/meetings/2024-01-03-FLEDGE-call-minutes.md new file mode 100644 index 000000000..db8e18553 --- /dev/null +++ b/meetings/2024-01-03-FLEDGE-call-minutes.md @@ -0,0 +1,578 @@ +# Protected Audience WICG Calls: Agenda & Notes + +Calls take place on most Wednesdays, at 11am US Eastern time; check [#88](https://github.com/WICG/turtledove/issues/88) for exceptions. + +That's 8am California = 5pm Paris time = 4pm UTC (during winter) + +This notes doc will be editable during the meeting — if you can only comment, hit reload + +Notes from past calls are all on GitHub [in this directory](https://github.com/WICG/turtledove/tree/main/meetings). + + +# Next video-call meeting: Wednesday Jan 3, 2024 + +To be added to a Google Calendar invitation for this meeting, join the Google Group https://groups.google.com/a/chromium.org/g/protected-audience-api-meetings/ + + +## Attendees: please sign yourself in! + + + +1. Michael Kleber (Google Privacy Sandbox) +2. Brian May (dstillery) +3. Shankar Venkataraman (Jivox) +4. Taranjit Singh(Jivox) +5. Kevin Lee (Google Privacy Sandbox) +6. Ricardo Bentin (Media.net) +7. Sven May (Google Privacy Sandbox) +8. Paul Jensen (Google Privacy Sandbox) +9. Amit Gupta (Jivox) +10. Sid Sahoo (Google Chrome) +11. Andrew Pascoe (NextRoll) +12. Matt Menke (Google Chrome) +13. Harshad Mane (PubMatic) +14. David Dabbs (Epsilon) +15. Joshua Prismon (Index Exchange) +16. Zach Edwards (Victory Medium) +17. Abishai Gray (Google Privacy Sandbox) +18. Orr Bernstein (Google Privacy Sandbox) +19. Jonasz Pamuła (RTB House) +20. Matt Davies (Bidswitch | Criteo) +21. Don Marti (Raptive) +22. Becky Hatley (Flashtalking) +23. Isaac Foster (MSFT Ads) +24. Timothy Taylor (Flashtalking) +25. Kenneth Kharma (OpenX) +26. Christopher Nachmias (Mediaocean / Flashtalking) +27. Jeroune Rhodes (Google Privacy Sandbox) +28. Laszlo Szoboszlai (Audigent) +29. Daniel Rojas (Google Chrome) +30. Jacob Goldman (Google Ad Manager) +31. Leeron Israel (Google Privacy Sandbox / Chrome) +32. Rotem Dar (eyeo) +33. Nick Llerandi (Triplelift) +34. Alex Cone (Google Privacy Sandbox) +35. Alex Peckham (Flashtalking) + + +## Note taker: Orr Bernstein + + +# Agenda + + +## Process reminder: Join WICG + +If you want to participate in the call, please make sure you join the WICG: https://www.w3.org/community/wicg/ + + +## Suggest agenda items here: + + + +* Isaac: + * Buyer/Seller Reporting Questions: https://github.com/WICG/turtledove/issues/682#issuecomment-1710965068 + * Multi Tag Support via “Mixed Ranking”: (really, this + multi tag + bit leak discussion and how we can be creative) https://github.com/WICG/turtledove/issues/846 + * Optional decouple bidding/reporting function urls to allow smaller k tuple: https://github.com/WICG/turtledove/issues/679#issuecomment-1703973736 +* Jonasz (RTB House) + * 3pc deprecation timeline: https://github.com/WICG/turtledove/issues/717#issuecomment-1847118918 +* Roni Gordon + * Sensitive signals - https://github.com/WICG/turtledove/issues/824 +* Matt Davies (Bidswitch) + * Origin / Traffic shaping - https://github.com/WICG/turtledove/issues/951 + * (Update: can remove from agenda, this has been addressed by conversation in the GitHub issue) +* Jeroune Rhodes (Google) + * **Join Privacy Sandbox Developer Webinar: Protected Audience API Reporting** + * The Google Privacy Sandbox team will be hosting our next series of webinars on the Protected Audience API. This set of webinars will focus on reporting and how you can measure data related to a Protected Audience auction. The first **Americas friendly session** is happening on** Jan. 16th 3-4 pm ET**. A second **EMEA friendly session** is happening **Jan. 18th 12-1 pm GMT**. A third **Japanese language session** will be held on **Jan 30th 9-11 am JST**. + * To join, please register below: + * AMER-friendly: [Register Here](https://rsvp.withgoogle.com/events/protected-audience-webinar-3-reporting-amer) + * EMEA-friendly: [Register Here](https://rsvp.withgoogle.com/events/protected-audience-webinar-3-reporting-emea) + * Japanese language: [Register Here](https://rsvp.withgoogle.com/events/protected-audience-office-hour-3) +* Shankar Venkataraman / Taranjit Singh / Amit Gupta (Jivox) + * Ad servers & IGs - https://github.com/WICG/turtledove/issues/924 + * https://github.com/GoogleChromeLabs/privacy-sandbox-dev-support/issues/201 + * We will review this deck - + + +# Notes + + +## 3pc deprecation timeline: https://github.com/WICG/turtledove/issues/717#issuecomment-1847118918 + + + +* Jonasz (RTB House) + * Can we get more guidance on the timeline for 3pc deprecation? + * We have a lot of coordination, monitoring, adjustments to perform before phase out. + * Start a discussion, get some clarity, thoughts of others on how this could look. Bump from 1% to 10% would be a great step in Q3 or Q4 +* Michael Kleber + * The actual answer to the question, at what time will be first be able to perform deprecation and removal of 3pc is not entirely in our own hands. Oversight of the UK’s Competition Market Authority. Testing that will be happening over the next five months, hope that many of you will be participating. After testing period, Chrome may say that they would like to remove 3pc. CMA might say yes, they might say you have to wait 60 days, or other answers. We cannot predict what is going to happen. + * When we start ramping things up, there’s a few things I can say. Not specifically about removal of 3pc, but generally on the rollout of potentially dangerous features on the web. There’s no chance that we’ll go from 1% to 100% directly. 10%, 25%, 50%, 100%, like Jonasz suggested, is the kind of thing we have done in the past. Ramping to something like 10% and holding there and watching and waiting to see any reports of breakage is consistent with the way we do things. + * The question about exactly when things are going to happen even aside from the CMA, we know that ads folks don’t like things to change in Q4, especially before Thanksgiving and between Thanksgiving and Christmas, we’ll try to be sensitive to those kinds of considerations as we choose the times at which things change. +* Jonasz + * Understand that further phaseout is conditional on CMA approval + * But we’d like to know what’s being considered if CMA agrees. + * When it comes to stages, it’s good to hear that that sounds good to you as well. But how long do stages last? If it’s a couple of weeks, that’s not very helpful. If it’s months, that seems more reasonable. + * There was a blogpost that announced the 1% on January 4th. But in that blogpost, it says that this is an important milestone on the path to full deprecation of 3pc in 2024. Is Google’s plan to do the full phaseout in 2024? The lack of clarity really paralyzes the whole effort. +* Michael + * Anything that involves what the CMA is going to do and how quickly is going to do it is a difficult conversation. I understand that further clarity would be helpful, we’ll try to say everything we can, but won’t be able to say everything you want to. +* Brian May + * The first CMA "60 days" pause is mandatory, not optional + * Is the goal of the Google team to try to identify benchmarks that would guide the deprecation? +* Michael + * Have not heard anything that’s based on a metrics-style feedback loop. We’ll have some timeline for when we plan to ramp up, and if something surprising happens, like if we learn about a large unexpected pocket of web breakage, we’ll reevaluate and potentially delay the ramp up. But I don’t know of any other example where that kind of closed loop thing has happened, wouldn’t expect it here either. +* Isaac Foster + * The thing that’s tricky for me to understand is, I would assume that you guys could potentially say, we’re not going from 1% to 100%. We’re going to express that timeline. There’s something about the agency being all on the CMA that I’m not understanding. Why can’t we just say, hey, let’s go with Jonasz’s proposal or something like that. +* Michael + * I have no desire to speak for the CMA, and wouldn’t want to even if I could. I have no idea what they may say about things in the future. +* Isaac + * It’s being presented as if all of the agency is on the CMA, and that doesn’t sound quite right to me. Is that the case? +* Michael + * The CMA is very interested in taking input from a large number of parties, and a large number of parties have strongly held opinions about the right way to do everything involving third-party cookie deprecation. So we will try to listen to what everybody says about the timing and be responsive to all of their needs. Whether it’s Chrome or the CMA or some combination thereof, the goal in general is to hear everybody’s needs and figure out some path between them all. Nobody is making a unilateral decision. +* Shankar Venkataraman + * When we do the rampup, my question is, how do we get the advertisers involved? Every advertiser has campaigns running. How do they build up their interest groups for that test campaign? If we have a timeline, we can provide our customers with guidance. +* Michael + * We understand that clarity will encourage everybody else in the industry that hasn’t yet taken the steps to start to do things. For example, as Jonas pointed out, when we go to 10%, when 10% of the traffic is without 3pc, that’s a very good time for those who haven’t yet taken the steps to start doing so. +* David Dabbs + * Very clear that it’s completely the CMA’s call, the timeline, in their recently quarterly notice to you all. The question here is, is the way you conduct the deprecation, can you say that? Are they calling the shots, approving or disapproving? +* Michael + * The only thing I can say is, the CMA is a regulator that is welcome to have authority over any part of the process they want to have authority over. If they want the timeline to be up to them, it’s entirely their choice for them to do so. +* Jonasz + * It seems that we haven’t learned much after today. Is there anything we can do to unblock this? It seems the decision really involves the CMA. My thinking is that being vocal about this - get this message to the CMA. +* Michael + * What I can tell you is that the CMA is surely aware of this discussion. They are paying attention. Whatever happens on GitHub they are very aware of. Further discussion on GitHub repository is good. Linking to the blog post from the GitHub issue may bring stuff to their attention if they don't already know. Direct feedback straight to the regulator in any fashion you choose. +* Harshad Mane + * Will Chrome take a couple of months to declare after the CMA decision? +* Michael + * We’ll all know that when it happens. +* David + * If you are going to write a letter or make a case to the CMA, they have a pretty stringent timeline. If you don’t get it to them by a certain time, they don’t look at it until the next quarter. So if you’re going to make a case, you should do it soon. +* Michael + * True that as a government regulator they have a clear process when they are in public comment, but they are always listening. +* Brian May + * Just to clarify for myself - we have until the end of June for the testing period, then the mandatory 60 day holdout period, which puts us at the end of August and the beginning of Q3, and the Q3 advertising period. If we go out another month, that puts us right at the door of Q4. Is it reasonable to assume that there will be a pause, or should we expect some possibility that in spite of code freezes and Q4 we might have additional deprecations. +* Michael + * As I said before, we (Chrome) understand that late Q4 is a bad time to do things that everybody wants to be in code freeze for the last six weeks of the year. Feel free to raise that feedback on GitHub and via all of the usual channels. +* Brian + * Is what I said about timeline correct? +* Michael + * What I can say is that [privacysandbox.com/timeline](privacysandbox.com/timeline) - middle of Q3 to middle of Q4 is what’s highlighted there as 3rd party cookie phaseout. + + +## Join Privacy Sandbox Developer Webinar: Protected Audience API Reporting + + + +* The Google Privacy Sandbox team will be hosting our next series of webinars on the Protected Audience API. This set of webinars will focus on reporting and how you can measure data related to a Protected Audience auction. The first **Americas friendly session** is happening on** Jan. 16th 3-4 pm ET**. A second **EMEA friendly session** is happening **Jan. 18th 12-1 pm GMT**. A third **Japanese language session** will be held on **Jan 30th 9-11 am JST**. +* To join, please register below: + * AMER-friendly: [Register Here](https://rsvp.withgoogle.com/events/protected-audience-webinar-3-reporting-amer) + * EMEA-friendly: [Register Here](https://rsvp.withgoogle.com/events/protected-audience-webinar-3-reporting-emea) + * Japanese language: [Register Here](https://rsvp.withgoogle.com/events/protected-audience-office-hour-3) +* Jeroune Rhodes (Google) + * Raising awareness about this. +* Michael + * This webinar is about the reporting aspects of the Protected Audience API at a variety of times in an attempt to be friendly to people from around the world. + + +## Retargeting - Adserver / advertiser perspective (Shankar Venkataraman, Jivox) + + + +* Ad servers & IGs - https://github.com/WICG/turtledove/issues/924 +* https://github.com/GoogleChromeLabs/privacy-sandbox-dev-support/issues/201 +* We will review this deck - +* Shankar Venkataraman / Taranjit Singh / Amit Gupta (Jivox) +* Shankar Venkataraman + * (presenting https://docs.google.com/presentation/d/1DhgWHIQ29rNik5JQ-k6mVxVqIkMyAYYcmg0HPLrfZus/edit#slide=id.p) + * Retargeting as it exists in the 3pc world + * User A and user B go to Advertiser TopShoes.com website.User A browses "boots" and user B browses "trainers”. + * The advertiser creates a retargeting pool against this behavior. The pool granularity could be “shoes” or “shoes.boots” or “shoes.trainers”. The campaign strategy decides which granularity to use when ad is served. + * The DSP usually combines these pools into a macro pool (to maximize reach), but the ad servers keep the granular pools. The ad server can use the granular pools across multiple DSPs. + * An ad is served to the user when visiting a publisher site like news-site.com. The creative served will have different product images (and corresponding offers / price etc) based on the pool to which the user belongs to. i.e. User A sees Boots and User B sees trainers. + * First question - are these namespaced to the advertiser domain? + * Advertiser works with multiple DSPs + * If each DSP is going to register multiple IGs, then the number of tags just multiplies + * What happens when advertiser stops + * Currently, interest groups are owned by the DSPs. + * Seems to be a problem for anybody who’s operating as an ad server +* Harshad + * Wanted to answer that right now in Protected Audience auctions, DSPs are invited to participate. IGs have to be owned by DSPs because advertisers are not directly invited into the auction. +* Shankar + * The audience is actually owned by the advertiser. If they put a promotion on their site by putting a new category, it’s almost impossible for a DSP to keep up with all this stuff. +* Harshad + * In the current cookie world, . +* Shankar + * The InterestGroup is not determined by the DSP. The advertiser tells the DSP that it wants to retarget to this audience. +* Harshad + * Maybe the advertiser will need to inform the DSP to generate interest groups based on a certain action. +* Shankar + * But that’s not the way it works. On an ecommerce site, there’s something called new <content?>. Could we make it easier for advertisers? They’re dependent on each of the DSPs to change the interest groups. Worst case, if the DSP of the advertiser changes, the DSP still owns the interest group. They could use the same interest group to target the competitor. +* Michael + * Let me answer the question you ask about granularity of targeting, e.g. shoes vs boots vs trainers. I would like to be clear that a single interest group can do all of those things. You do not need to think of this as putting a person into three different interest groups in order to signify three different details of what the person showed interest in. It’s not a boolean. Instead you could put a person into a single interest group that’s related to the advertiser site that they were visiting, inside the interest group, the user bidding signals could have a list of all of the subcategories of shoes and how much the person seems to be interested in each of those categories. And all future ads or ad campaigns that buy using that interest group can customize their targeting. An interest group is a much richer object, and it has the ability to hold a lot of information and can serve many different ad campaigns based on that. +* Shankar + * We couldn’t figure that out from the API spec. +* Michael + * Happy to talk about this here, happy to talk about this on the GitHub issue. Don’t feel like you need to put a user into a lot of interest groups in order to advertise on a lot of facets of the user behavior. + * The second question is about namespacing and multiple DSPs. You’re correct that the way we designed the PA API, every interest group is owned by a single DSP. The DSP also has bidding logic. If an advertiser wants to have three different DSPs, exactly as Harshad was just saying, there would be three different joinAdInterestGroup calls on their page, from all three different DSPs, so the user can end up in all of their interest groups, each out representing a single DSP’s ability to bid on behalf of that advertiser. +* Shankar + * Impression control - if I want to hit the user only once per day, but there are three DSPs running. +* Michael + * How does that work today? If you have three DSPs each independently making buying decisions, and each doesn’t know the buying decisions of the other ones. +* Shankar + * Right now, it’s messed up too, I was hoping you could solve it. +* Michael + * We have not tried to solve exactly the question that you’re asking about. But it’s not implausible that we could do so. Right now, everything is scoped at the DSP level. Any kind of cross-DSP rate limiting in the auction would be new work. +* Shankar + * Why I was pushing for one interest group per advertiser is because of the corollary. If you own the shared storage domain at that point, then you … +* Michael + * Right now, the way things would work, you could only do the control at the advertiser level across different DSPs in a post-auction sort of way. Shared storage is available, and when it goes to show the creative, it could check shared storage, and if it’s already shown the ad, it could show a different ad. This works well for a goal like "creative rotation": An ad could look in different ways depending on what’s already been shown that day, using the Shared Storage and selectURL functionality. That is available today and would work across DSPs. + * Within the auction, information is at an interest group-by-interest group level, and cannot be shared across interest groups, and thus cannot be shared across DSPs either. But if we have multiple layers of ownership, an advertiser layer and a DSP layer, that is a feature we could add. It could be compatible with how Privacy Sandbox works because you’re asking for interest groups to be either in or not in the auction based on information on the device. It’s not a feature we’ve already built. We’ve already built negative targeting based on interest groups for contextually targeted ads. The thing you're asking for is related, because it’s negative targeting of interest groups based on other interest groups. +* Taranjit Singh + * To cover Shankar’s point, we already discussed that we could use negative targeting, But not sure if the spec itself explains it. When a regular interest group wins an auction, there’s no mapping in terms of that based on the bidding signal or user input signal. +* Michael + * This is getting at a change that happened over course of discussion of the PA API. Each ad in the IG can carry a BuyerAndSellerReportingID that might help with this. + * The interest group name could be very coarse. The user data in that IG could be everything that IG knows about what I’ve done on that site. It’s possible to push ads into that IG based on the full set of information about my behavior on 1 site. When those ads are rendered, they can’t have that identifier, because none of those ads would then have met the kAnon threshold. You could have all of the ads and all of the ad campaigns playing together in a single interest group and then part of that reporting structure, and those all could be targeted based on different facets of my behavior on a single site. +* Anthony Yam + * DSP is also the ad server, kind of critical for us, when you’re saying that ads can be added at creation and update time, the tech partner knows what the ads are, not the DSP. Back to the comment before, can only the actual creative partner use shared storage? +* Michael + * Already over time, so can’t discuss in detail. Already an issue that was opened by the index exchange folks that is about allowing things to load from different domains so that it’s possible for an IG to talk directly to an ad server of a DSP to get a creative. But the metadata for the creative has to be enough to know how to do things at auction time instead of post auction render time. + +## Here are the in-call sidebar chat comments… + +Don Marti + +11:11 AM + +https://manifold.markets/DonMarti7bd2/will-google-chrome-support-thirdpar + + \ +Harshad Mane + +11:13 AM + +This year :) + + \ +David Dabbs + +11:13 AM + +And an epic election cycle! + + \ +Kevin Lee + +11:17 AM + +jonasz, would you be able to link that article you just mentioned? + + \ +Jonasz Pamuła + +11:17 AM + +Sure:[ https://blog.google/products/chrome/privacy-sandbox-tracking-protection/](https://blog.google/products/chrome/privacy-sandbox-tracking-protection/) + + \ +Kevin Lee + +11:17 AM + +thank you! + + \ +Jonasz Pamuła + +11:17 AM + +" We'll roll this out to 1% of Chrome users globally, a key milestone in our Privacy Sandbox initiative to phase out third-party cookies for everyone in the second half of 2024" + + \ +David Dabbs + +11:22 AM + +We are all empowered to write "amicus brief" memos to the CMA making suggestions about how the rampdown is conducted. + + \ +Warren Fernandes + +11:25 AM + +Is there a resource available that details which privacy sandbox APIs require enrolment? + + \ +David Dabbs + +11:25 AM + +Yes. The ensollment GH repo. + + \ +Sid Sahoo + +11:25 AM + +https://goo.gle/privacy-sandbox-enroll + + \ +Warren Fernandes + +11:25 AM + +Thanks + +David Dabbs + +11:25 AM + +https://github.com/privacysandbox/attestation/ + + \ +Brian May + +11:29 AM + +Perhaps an open letter from industry participants? + + \ +Harshad Mane + +11:29 AM + ++1 + + \ +Joshua Prismon + +11:29 AM + +Will need to drop for a moment. Be back in 5 minutes. + + \ +David Dabbs + +11:32 AM + +Can and will be used against you... + + \ +Warren Fernandes + +11:33 AM + +Wrt the enrollment mentions of "site" could this apply to SSP domains that span across websites? + +i.e. can a single domain enroll and use the APIs across websites? + + \ +Isaac Foster + +11:34 AM + +the domain that would be checked is the on of the IG owner or auction runner + +so fairly sure the answer to your question is yes + + \ +Owen Ridolfi + +11:34 AM + +IG stands for? + + \ +Warren Fernandes + +11:34 AM + +Perfect, thanks Isaac + + \ +Isaac Foster + +11:34 AM + +interest group + +the owner of the IG is, presumably, the DSP, generally not the advertiser themselves + +(it can be ofc) + + \ +David Dabbs + +11:35 AM + +"Third-Party Cookie Phaseout" sung to the tune of "Tenth Avenue Freeze out" + + \ +Jonasz Pamuła + +11:35 AM + +If you like, please add your thoughts about timeline & the importance of knowing it early in[ https://github.com/WICG/turtledove/issues/717](https://github.com/WICG/turtledove/issues/717) to strengthen the message + +Brian May + +11:36 AM + +Thanks Jonasz. + +Isaac Foster + +11:39 AM + +sorry ntd for internal fledge meeting + +thank you all + +Warren Fernandes + +11:42 AM + +BTW is there any guidance on the role GAM plays in the FLEDGE process + +Sid Sahoo + +11:47 AM + +https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing + +Warren Fernandes + +11:49 AM + +Thanks + +B. McLeod Sims + +11:49 AM + +is there a link to the spec explaining that? + +Sid Sahoo + +11:49 AM + +That = GAM's role? + +B. McLeod Sims + +11:49 AM + +sorry the IG being able to have non boolean behavior + +Sid Sahoo + +11:51 AM + +https://github.com/WICG/turtledove/blob/main/FLEDGE.md#11-joining-interest-groups + +Owen Ridolfi + +11:52 AM + +the ad server would control it. + +Paul Jensen + +11:52 AM + +@B. McLeod Sims: The IG contains a userBiddingSignals field that can contain arbitrary information. This information is available at bidding (i.e. generateBid() time). This is described in the Explainer link that Sid provided, and is also in our spec. + +B. McLeod Sims + +11:53 AM + +cool, i missed that bit, thank you for the explanation + +David Dabbs + +11:54 AM + +renderURLs do not need to be same-origin with the bidding DSP(s), right? + +Paul Jensen + +11:54 AM + +Right + +David Dabbs + +11:54 AM + +Different "creative expression. of that ad. + +David Dabbs + +11:56 AM + +Tweak of negative targeting + +Harshad Mane + +11:57 AM + +is there a way to create an IG under advertiser-1-domain and delegate it to dsp-1-domain to participate in auction? + +Sid Sahoo + +11:58 AM + +https://github.com/WICG/turtledove/blob/main/FLEDGE.md#13-permission-delegation + +David Dabbs + +11:59 AM + +@Harshad the bidding logic URL should be same-origin with the IG owner. I suppose an advertiser could use an origin they control and delegate control to specific PAAPI-oriented subdomains. + +Sid Sahoo + +11:59 AM + +When you delegate permission, the IG is owned by advertiser-1-domain + +Yes @David, and the bidding script needs hosted on this domain as well + +Matt Davies + +12:00 PM + +Isn't there a minimum amount of users that can be targeting within an interest group? + +\*targeted + +David Dabbs + +12:00 PM + +Oh, Harshad you were asking about making the joinAdInterestGroup() call on the advertiser's property but the IG owner is dsp.example. Yes this is possible with the correct permissions files, &c. in place. See the explainer. + +Party over, oops, outta time. + +Sid Sahoo + +12:01 PM + +@Matt: There is a k-anon requirement, not at the IG level; here are more details:[ https://developers.google.com/privacy-sandbox/relevance/protected-audience-api/k-anonymity?hl=en](https://developers.google.com/privacy-sandbox/relevance/protected-audience-api/k-anonymity?hl=en) + +Matt Davies + +12:01 PM + +Thanks + +Harshad Mane + +12:02 PM + +Thank you for inputs Sid, David and Matt... I will check it offline + +Shankar Venkataraman + +12:04 PM + +https://docs.google.com/presentation/d/1DhgWHIQ29rNik5JQ-k6mVxVqIkMyAYYcmg0HPLrfZus/edit?usp=drivesdk diff --git a/meetings/README.md b/meetings/README.md index ba486c23a..b410bceec 100644 --- a/meetings/README.md +++ b/meetings/README.md @@ -2,6 +2,6 @@ We are holding regular phone calls to resolve the details of the FLEDGE proposal. These calls should be good places for a dedicated group to address open issues and suggest technical fixes, or to talk about particular use cases and how they can be met using FLEDGE. -Calls generally take place every second Wednesday, at 11am US Eastern time. For call details see https://github.com/WICG/turtledove/issues/88. +Calls generally take place every Wednesday, at 11am US Eastern time. For call details see https://github.com/WICG/turtledove/issues/88. The minutes of each call are posted in this directory. diff --git a/spec.bs b/spec.bs index afb399ffc..e3a213c63 100644 --- a/spec.bs +++ b/spec.bs @@ -96,6 +96,8 @@ spec:infra; type:dfn; text:user agent margin: 1em 0; } +dl { padding-left: 1em; } + /* domintro from https://resources.whatwg.org/standard.css */ .domintro { position: relative; @@ -218,9 +220,9 @@ This is detectable because it can change the set of fields that are read from th {{TypeError}} is eventually thrown, but it will never change whether the call succeeds or fails. -1. If [=this=]'s [=relevant global object=]'s [=associated Document=] is not [=allowed to use=] the - "[=join-ad-interest-group=]" [=policy-controlled feature=], then [=exception/throw=] a - "{{NotAllowedError}}" {{DOMException}}. +1. Let |global| be [=this=]'s [=relevant global object=]. +1. If |global|'s [=associated Document=] is not [=allowed to use=] the "[=join-ad-interest-group=]" + [=policy-controlled feature=], then [=exception/throw=] a "{{NotAllowedError}}" {{DOMException}}. 1. Let |frameOrigin| be [=this=]'s [=relevant settings object=]'s [=environment settings object/origin=]. 1. [=Assert=] that |frameOrigin| is not an [=opaque origin=] and its [=origin/scheme=] is "`https`". 1. Let |interestGroup| be a new [=interest group=]. @@ -345,9 +347,11 @@ This is detectable because it can change the set of fields that are read from th 1. [=parallel queue/enqueue steps|Enqueue the following steps=] to |queue|: 1. Let |permission| be the result of [=checking interest group permissions=] with |interestGroup|'s [=interest group/owner=], |frameOrigin|, and "`join`". - 1. If |permission| is false, then [=queue a task=] to [=reject=] |p| with a - "{{NotAllowedError}}" {{DOMException}} and do not run the remaining steps. - 1. [=Queue a task=] to [=resolve=] |p| with `undefined`. + 1. If |permission| is false, then [=queue a global task=] on [=DOM manipulation task source=], + given |global|, to [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and abort these + steps. + 1. [=Queue a global task=] on [=DOM manipulation task source=], given |global|, to [=resolve=] |p| + with `undefined`. 1. If the browser is currently storing an interest group with `owner` and `name` that matches |interestGroup|, then set the [=interest group/bid counts=], [=interest group/join counts=], and [=interest group/previous wins=] of @@ -452,6 +456,8 @@ integer |maxIgs|:

Leaving Interest Groups

+

leaveAdInterestGroup()

+ *This first introductory paragraph is non-normative.* {{Window/navigator}}.{{Navigator/leaveAdInterestGroup()}} removes a user from a particular interest @@ -474,37 +480,40 @@ dictionary AuctionAdInterestGroupKey { The leaveAdInterestGroup(group) method steps are: -1. Let |frameOrigin| be [=this=]'s [=relevant settings object=]'s - [=environment settings object/origin=]. +1. Let |global| be [=this=]'s [=relevant global object=]. +1. Let |frameOrigin| be |global|'s [=environment settings object/origin=]. 1. [=Assert=] that |frameOrigin| is not an [=opaque origin=] and its [=origin/scheme=] is "`https`". 1. Let |p| be [=a new promise=]. 1. If |group| [=map/is empty=]: - 1. Let |instance| be [=this=]'s [=relevant global object=]'s [=Window/browsing context=]'s + 1. Let |instance| be |global|'s [=Window/browsing context=]'s [=browsing context/fenced frame config instance=]. - 1. If |instance| is null, [=exception/throw=] a {{TypeError}}. + 1. If |instance| is null, then return. 1. Let |interestGroup| be |instance|'s [=fenced frame config instance/interest group descriptor=]. 1. Run these steps [=in parallel=]: - 1. [=Queue a task=] to [=resolve=] |p| with `undefined`. + 1. [=Queue a global task=] on [=DOM manipulation task source=], given |global|, to [=resolve=] + |p| with `undefined`. 1. If |interestGroup| is not null: 1. Let |owner| be |interestGroup|'s [=interest group descriptor/owner=]. 1. If |owner| is [=same origin=] with |frameOrigin|, then [=list/remove=] [=interest groups=] from the [=user agent=]'s [=interest group set=] whose [=interest group/owner=] is |owner| and [=interest group/name=] is |interestGroup|'s [=interest group descriptor/name=]. 1. Otherwise: - 1. If [=this=]'s [=relevant global object=]'s [=associated Document=] is not [=allowed to use=] the + 1. If |global|'s [=associated Document=] is not [=allowed to use=] the "[=join-ad-interest-group=]" [=policy-controlled feature=], then [=exception/throw=] a "{{NotAllowedError}}" {{DOMException}}. - Note: both joining and leaving interest groups use the "join-ad-interest-group" feature. + Note: Both joining and leaving interest groups use the "join-ad-interest-group" feature. 1. Let |owner| be the result of [=parsing an https origin=] with |group|["{{AuctionAdInterestGroupKey/owner}}"]. 1. If |owner| is failure, [=exception/throw=] a {{TypeError}}. 1. Run these steps [=in parallel=]: 1. Let |permission| be the result of [=checking interest group permissions=] with |owner|, |frameOrigin|, and "`leave`". - 1. If |permission| is false, then [=queue a task=] to [=reject=] |p| with a - "{{NotAllowedError}}" {{DOMException}} and do not run the remaining steps. - 1. [=Queue a task=] to [=resolve=] |p| with `undefined`. + 1. If |permission| is false, then [=queue a global task=] on [=DOM manipulation task source=], + given |global|, to [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and abort + these steps. + 1. [=Queue a global task=] on [=DOM manipulation task source=], given |global|, to [=resolve=] + |p| with `undefined`. 1. [=list/Remove=] [=interest groups=] from the [=user agent=]'s [=interest group set=] whose [=interest group/owner=] is |owner| and [=interest group/name=] is |group|["{{AuctionAdInterestGroupKey/name}}"]. @@ -512,6 +521,54 @@ The leaveAdInterestGroup(group) method steps are +

clearOriginJoinedAdInterestGroups()

+ +*This first introductory paragraph is non-normative.* + +{{Window/navigator}}.{{Navigator/clearOriginJoinedAdInterestGroups()}} removes a user from +[=interest groups=] whose [=interest group/joining origin=] is the associated +{{Navigator}}'s [=relevant settings object=]'s [=environment/top-level origin=]. + + + +[SecureContext] +partial interface Navigator { + Promise<undefined> clearOriginJoinedAdInterestGroups( + USVString owner, optional sequence<USVString> interestGroupsToKeep = []); +}; + + +
+ +The clearOriginJoinedAdInterestGroups(|owner|, |interestGroupsToKeep|) +method steps are: + +1. Let |frameOrigin| be [=this=]'s [=relevant settings object=]'s + [=environment settings object/origin=]. +1. [=Assert=] that |frameOrigin| is not an [=opaque origin=] and its [=origin/scheme=] is "`https`". +1. Let |p| be [=a new promise=]. +1. Let |global| be [=this=]'s [=relevant global object=]. +1. If |global|'s [=associated Document=] is not [=allowed to use=] the + "[=join-ad-interest-group=]" [=policy-controlled feature=], then [=exception/throw=] a + "{{NotAllowedError}}" {{DOMException}}. + + Note: Both joining and leaving interest groups use the "join-ad-interest-group" feature. +1. Let |ownerOrigin| be the result of [=parsing an https origin=] with |owner|. +1. If |ownerOrigin| is failure, [=exception/throw=] a {{TypeError}}. +1. Run these steps [=in parallel=]: + 1. Let |permission| be the result of [=checking interest group permissions=] with + |ownerOrigin|, |frameOrigin|, and "`leave`". + 1. If |permission| is false, then [=queue a global task=] on the [=DOM manipulation task source=] + given |global|, [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and abort these steps. + 1. [=Queue a global task=] on the [=DOM manipulation task source=] given |global|, to [=resolve=] |p| + with {{undefined}}. + 1. [=list/Remove=] [=interest groups=] from the [=user agent=]'s [=interest group set=] + whose [=interest group/owner=] is |ownerOrigin|, whose [=interest group/joining origin=] is + |frameOrigin|, and whose [=interest group/name=] is not in |interestGroupsToKeep|. +1. Return |p|. + +
+

Running Ad Auctions

*This first introductory paragraph is non-normative.* @@ -536,6 +593,7 @@ dictionary AuctionAdConfig { USVString trustedScoringSignalsURL; sequence interestGroupBuyers; Promise auctionSignals; + record requestedSize; Promise sellerSignals; Promise directFromSellerSignalsHeaderAdSlot; unsigned long long sellerTimeout; @@ -543,6 +601,7 @@ dictionary AuctionAdConfig { USVString sellerCurrency; Promise> perBuyerSignals; Promise> perBuyerTimeouts; + Promise> perBuyerCumulativeTimeouts; record perBuyerGroupLimits; record perBuyerExperimentGroupIds; record> perBuyerPrioritySignals; @@ -592,12 +651,12 @@ The runAdAuction(|config|) method steps are: 1. Let |queue| be the result of [=starting a new parallel queue=]. 1. [=parallel queue/enqueue steps|Enqueue the following steps=] to |queue|: 1. Let |winnerInfo| be the result of running [=generate and score bids=] with |auctionConfig|, - null, |global|, |settings|, and |bidIgs|. + null, |global|, |settings|'s [=environment/top-level origin=], and |bidIgs|. 1. If |winnerInfo| is failure, then [=queue a global task=] on [=DOM manipulation task source=], given |global|, to [=reject=] |p| with a "{{TypeError}}". - 1. If |winnerInfo| is null or |winnerInfo|'s [=leading bid info/leading bid=] is null: - 1. [=Queue a global task=] on [=DOM manipulation task source=], given |global|, to resolve |p| - with null. + 1. If |winnerInfo| is null or |winnerInfo|'s [=leading bid info/leading bid=] is null, then + [=queue a global task=] on [=DOM manipulation task source=], given |global|, to resolve |p| with + null. 1. Otherwise: 1. Let |winner| be |winnerInfo|'s [=leading bid info/leading bid=]. 1. Let |fencedFrameConfig| be the result of [=filling in a pending fenced frame config=] with @@ -721,25 +780,22 @@ To fill in a pending fenced frame config given a [=fenced frame confi : name :: |winningBid|'s [=generated bid/interest group=]'s [=interest group/name=] +1. Let |fencedFrameReportingMap| be the [=map=] «[ "`buyer`" → «», "`seller`" → «» ]». +1. If |auctionConfig|'s [=auction config/component auctions=] is [=list/empty=], then [=map/set=] + |fencedFrameReportingMap|["`component-seller`"] to an empty [=list=] «». 1. Set |pendingConfig|'s [=fenced frame config/fenced frame reporting metadata=] to a [=struct=] with the following [=struct/items=]: : [=fenced frame reporting metadata/value=] - :: If |auctionConfig|'s [=auction config/component auctions=] is [=list/empty=] (i.e., if - there was no component auction), then a [=struct=] with the following [=struct/items=]: + :: A [=struct=] with the following [=struct/items=]: : [=fenced frame reporting metadata/fenced frame reporting map=] - :: a [=map=] «[ "buyer" → «», "seller" → «»]» + :: |fencedFrameReportingMap| : [=fenced frame reporting metadata/direct seller is seller=] - :: true + :: true if |auctionConfig|'s [=auction config/component auctions=] is [=list/empty=], false + otherwise - Otherwise (i.e., if there was a component auction), a [=struct=] with the following - [=struct/items=]: - : [=fenced frame reporting metadata/fenced frame reporting map=] - :: a [=map=] «[ "buyer" → «», "seller" → «», - "component-seller" → «»]» - - : [=fenced frame reporting metadata/direct seller is seller=] - :: false + : [=fenced frame reporting metadata/allowed reporting origins=] + :: |winningBid|'s [=generated bid/bid ad=]'s [=interest group ad/allowed reporting origins=] : [=fenced frame reporting metadata/visibility=] :: "`opaque`" @@ -776,15 +832,10 @@ To asynchronously finish reporting given a 1. Let |buyerMap| be |leadingBidInfo|'s [=leading bid info/buyer reporting result=]'s [=reporting result/reporting beacon map=]. 1. If |buyerMap| is null, set |buyerMap| to an empty [=map=] «[]». - 1. Let |allowedReportingOrigins| be |leadingBidInfo|'s [=leading bid info/leading bid=]'s - [=generated bid/bid ad=]'s [=interest group ad/allowed reporting origins=]. 1. Let |macroMap| be |leadingBidInfo|'s [=leading bid info/buyer reporting result=]'s [=reporting result/reporting macro map=]. - 1. TODO: Pass |macroMap| and |allowedReportingOrigins| to [=Finalize a reporting destination=] - when it is updated to take the parameters. May need to convert |macroMap| to a list, based - on what that function expects. 1. [=Finalize a reporting destination=] with |reportingMap|, - {{FenceReportingDestination/buyer}}, and |buyerMap|. + {{FenceReportingDestination/buyer}}, |buyerMap|, and |macroMap|. 1. [=Send report=] to |leadingBidInfo|'s [=leading bid info/buyer reporting result=]'s [=reporting result/report url=]. 1. Set |buyerDone| to true. @@ -924,6 +975,24 @@ To validate and convert auction ad config given an {{AuctionAdConfig} * To parse the value |result|, set |auctionConfig|'s [=auction config/auction signals=] to the result of [=serializing a JavaScript value to a JSON string=]. * To handle an error, set |auctionConfig|'s [=auction config/auction signals=] to failure. +1. If |config|["{{AuctionAdConfig/requestedSize}}"] [=map/exists=]: + 1. Let |requestedSize| be |config|["{{AuctionAdConfig/requestedSize}}"] + 1. If |requestedSize|["height"] does not [=map/exist=], throw a {{TypeError}}. + + 1. If |requestedSize|["width"] does not [=map/exist=], throw a {{TypeError}}. + + 1. Let |width| and |widthUnit| be the dimension and dimension unit that results from running + [=parse an AdRender dimension value=] with |requestedSize|["width"], respectively. + 1. If |width| is null, throw a {{TypeError}}. + + 1. Let |height| and |heightUnit| be the dimension and dimension unit that results from running + [=parse an AdRender dimension value=] with |requestedSize|["height"], respectively. + 1. If |height| is null, throw a {{TypeError}}. + + 1. Let |adSize| be a new [=ad size=]. + 1. Set |adSize|'s [=ad size/width=] to |width|, [=ad size/width units=] to |widthUnit|, + [=ad size/height=] to |height|, [=ad size/height units=] to |heightUnit|. + 1. Set |auctionConfig|'s [=auction config/requested size=] to |adSize|. 1. If |config|["{{AuctionAdConfig/sellerSignals}}"] [=map/exists=]: 1. Set |auctionConfig|'s [=auction config/seller signals=] to |config|["{{AuctionAdConfig/sellerSignals}}"]. @@ -945,11 +1014,26 @@ To validate and convert auction ad config given an {{AuctionAdConfig} 1. [=Handle an input promise in configuration=] given |auctionConfig| and |config|["{{AuctionAdConfig/additionalBids}}"]: - Note: The JavaScript code calling {{Navigator/runAdAuction()}} is responsible for not resolving - |config|["{{AuctionAdConfig/additionalBids}}"] until an associated [=request=] whose - [=request/initiator type=] is `"fetch"` and the {{RequestInit/adAuctionHeaders}} option set to - `true` returns a response, or alternatively, not calling {{Navigator/runAdAuction()}} until the - [=request=] returns a response. + Note: The JavaScript code calling {{Navigator/runAdAuction()}} is responsible for *not* + resolving |config|["{{AuctionAdConfig/additionalBids}}"] until additional bids have been + retrieved from one or more [:Ad-Auction-Additional-Bid:] headers, as resolving this Promise + early would cause a race condition in which additional bids might not be included in the + auction. There are two ways that additional bids can be retrieved. The first is for the + JavaScript code to issue a [=request=] whose [=request/initiator type=] is `"fetch"` and whose + {{RequestInit/adAuctionHeaders}} option is set to `true`. The JavaScript code has to resolve + |config|["{{AuctionAdConfig/additionalBids}}"] after the corresponding call to + {{WindowOrWorkerGlobalScope/fetch()}} has resolved to a [=response=]. + The JavaScript code can also choose to wait to call {{Navigator/runAdAuction()}} until after + the corresponding call to {{WindowOrWorkerGlobalScope/fetch()}} has resolved to a response, + and can then immediately resolve |config|["{{AuctionAdConfig/additionalBids}}"]. + + The second way that additional bids can be retrieved is by issuing an + iframe navigation + request with the <{iframe/adauctionheaders}> content attribute set to `true`. + In this case, the JavaScript code is retrieved as part of the iframe navigation response, + at which point the JavaScript code in the iframe makes the call to + {{Navigator/runAdAuction()}}, and |config|["{{AuctionAdConfig/additionalBids}}"] can be + immediately resolved. * To parse the value |result|: 1. Set |auctionConfig|'s [=auction config/expects additional bids=] to false. @@ -958,14 +1042,29 @@ To validate and convert auction ad config given an {{AuctionAdConfig} 1. If |config|["{{AuctionAdConfig/directFromSellerSignalsHeaderAdSlot}}"] [=map/exists=]: 1. [=Handle an input promise in configuration=] given |auctionConfig| and |config|["{{AuctionAdConfig/directFromSellerSignalsHeaderAdSlot}}"]: - - Note: The JavaScript code calling {{Navigator/runAdAuction()}} is responsible for resolving - |config|["{{AuctionAdConfig/directFromSellerSignalsHeaderAdSlot}}"] only when the {{Promise}} - returned from an associated [=request=], whose [=request/initiator type=] is `"fetch"` and the - {{RequestInit/adAuctionHeaders}} option set to `true`, resolves or rejects. Otherwise, there - will be a race condition that the worklet can run without the direct from seller signals that - it needs. See [handling direct from seller signals](#handling-direct-from-seller-signals) for - details. + + Note: The JavaScript code calling {{Navigator/runAdAuction()}} is responsible for *not* + resolving |config|["{{AuctionAdConfig/directFromSellerSignalsHeaderAdSlot}}"] + until direct from seller signals have been retrieved from one or more [:Ad-Auction-Signals:] + headers, as resolving this Promise early would cause a race condition in which the worklet + might run without the direct from seller signals that it needs. There are two ways that + direct from seller signals can be retrieved. The first is for the JavaScript code to issue a + [=request=] whose [=request/initiator type=] is `"fetch"` and whose + {{RequestInit/adAuctionHeaders}} option is set to `true`. The JavaScript code has to resolve + |config|["{{AuctionAdConfig/directFromSellerSignalsHeaderAdSlot}}"] after the corresponding + call to {{WindowOrWorkerGlobalScope/fetch()}} has resolved to a [=response=]. The JavaScript + code can also choose to wait to call {{Navigator/runAdAuction()}} until after the + corresponding call to {{WindowOrWorkerGlobalScope/fetch()}} has resolved to a [=response=], + and can then immediately resolve + |config|["{{AuctionAdConfig/directFromSellerSignalsHeaderAdSlot}}"]. The second way that + direct from seller signals can be retrieved is by issuing an + iframe navigation + request with the <{iframe/adauctionheaders}> content attribute set to `true`. + In this case, the JavaScript code is retrieved as part of the iframe navigation response, + at which point the JavaScript code in the iframe makes the call to + {{Navigator/runAdAuction()}}, and + |config|["{{AuctionAdConfig/directFromSellerSignalsHeaderAdSlot}}"] can be can be specified + directly without a Promise. * To parse the value |result|: 1. Set |auctionConfig|'s [=auction config/direct from seller signals header ad slot=] to @@ -1000,23 +1099,36 @@ To validate and convert auction ad config given an {{AuctionAdConfig} 1. [=map/Set=] |auctionConfig|'s [=auction config/per buyer signals=][|buyer|] to |signalsString|. * To handle an error, set |auctionConfig|'s [=auction config/per buyer signals=] to failure. -1. If |config|["{{AuctionAdConfig/perBuyerTimeouts}}"] [=map/exists=]: - 1. Set |auctionConfig|'s [=auction config/per buyer timeouts=] to - |config|["{{AuctionAdConfig/perBuyerTimeouts}}"]. - 1. [=Handle an input promise in configuration=] given |auctionConfig| and |auctionConfig|'s - [=auction config/per buyer timeouts=]: - * To parse the value |result|: - 1. Set |auctionConfig|'s [=auction config/per buyer timeouts=] to a new [=ordered map=] whose - [=map/keys=] are [=origins=] and whose [=map/values=] are [=durations=] in milliseconds. - 1. [=map/For each=] |key| → |value| of |result|: - 1. If |key| is "*", then set |auctionConfig|'s [=auction config/all buyers timeout=] - to |value| in milliseconds or 500 milliseconds, whichever is smaller, and - [=iteration/continue=]. - 1. Let |buyer| be the result of [=parsing an https origin=] with |key|. If |buyer| is - failure, [=exception/throw=] a {{TypeError}}. - 1. [=map/Set=] |auctionConfig|'s [=auction config/per buyer timeouts=][|buyer|] to - |value| in milliseconds or 500 milliseconds, whichever is smaller. - * To handle an error, set |auctionConfig|'s [=auction config/per buyer timeouts=] to failure. +1. For each |idlTimeoutMember|, |perBuyerTimeoutField|, |allBuyersTimeoutField| in the following table + + + + + + + + + + + + +
IDL timeout memberPer buyer timeout fieldAll buyers timeout field
"{{AuctionAdConfig/perBuyerTimeouts}}"[=auction config/per buyer timeouts=][=auction config/all buyers timeout=]
"{{AuctionAdConfig/perBuyerCumulativeTimeouts}}"[=auction config/per buyer cumulative timeouts=][=auction config/all buyers cumulative timeout=]
+ 1. If |config| [=map/contains=] |idlTimeoutMember|: + 1. Set |auctionConfig|'s |perBuyerTimeoutField| to |config|[|idlTimeoutMember|]. + 1. [=Handle an input promise in configuration=] given |auctionConfig| and |auctionConfig|'s + |perBuyerTimeoutField|: + * To parse the value |result|: + 1. Set |auctionConfig|'s |perBuyerTimeoutField| to a new [=ordered map=] whose + [=map/keys=] are [=origins=] and whose [=map/values=] are [=durations=] in milliseconds. + 1. [=map/For each=] |key| → |value| of |result|: + 1. If |perBuyerTimeoutField| is "{{AuctionAdConfig/perBuyerTimeouts}}", and + |value| > 500, then set |value| to 500. + 1. If |key| is "*", then set |auctionConfig|'s |allBuyersTimeoutField| to |value| in + milliseconds, and [=iteration/continue=]. + 1. Let |buyer| be the result of [=parsing an https origin=] with |key|. If |buyer| is + failure, [=exception/throw=] a {{TypeError}}. + 1. [=map/Set=] |auctionConfig|'s |perBuyerTimeoutField|[|buyer|] to |value| in milliseconds. + * To handle an error, set |auctionConfig|'s |perBuyerTimeoutField| to failure. 1. If |config|["{{AuctionAdConfig/perBuyerGroupLimits}}"] [=map/exists=], [=map/for each=] |key| → |value| of |config|["{{AuctionAdConfig/perBuyerGroupLimits}}"]: 1. If |value| is 0, then return failure. @@ -1107,9 +1219,10 @@ To validate and convert auction ad config given an {{AuctionAdConfig} [=interest group/name=] is |ig|'s [=interest group/name=], return if none found. 1. Let |win| be a new [=previous win=]. 1. Set |win|'s [=previous win/time=] to the [=current wall time=]. - 1. Let |ad| be the [=ad descriptor=] from |ig|'s [=interest group/ads=] whose - [=ad descriptor/url=] is |bid|'s [=generated bid/ad descriptor=] - [=ad descriptor/url=], return if none found. + 1. Let |ad| be an [=interest group ad=] whose [=interest group ad/render url=] is |bid|'s + [=generated bid/bid ad=]'s [=interest group ad/render url=], and whose + [=interest group ad/metadata=] is |bid|'s [=generated bid/bid ad=]'s + [=interest group ad/metadata=]. 1. Set |win|'s [=previous win/ad json=] to the result of [=serializing an Infra value to a JSON string=] given |ad|. 1. [=list/Append=] |win| to |loadedIg|'s [=interest group/previous wins=]. @@ -1207,8 +1320,8 @@ and a [=moment=] |auctionStartTime|:
To generate and score bids given an [=auction config=] |auctionConfig|, an -[=auction config=]-or-null |topLevelAuctionConfig|, a [=global object=] |global|, an -[=environment settings object=] |settings|, and a [=list=] of [=interest groups=] |bidIgs|: +[=auction config=]-or-null |topLevelAuctionConfig|, a [=global object=] |global|, an [=origin=] +|topLevelOrigin|, and a [=list=] of [=interest groups=] |bidIgs|: 1. [=Assert=] that these steps are running [=in parallel=]. 1. Let |auctionStartTime| be the [=current wall time=]. 1. Let |decisionLogicScript| be the result of [=fetching script=] with |auctionConfig|'s @@ -1219,7 +1332,7 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. Let |leadingBidInfo| be a new [=leading bid info=]. 1. Let |queue| be the result of [=starting a new parallel queue=]. 1. Let |capturedAuctionHeaders| be |global|'s [=associated Document's=] [=node navigable's=] - [=traversable navigable's=] [=traversable navigable/captured ad auction headers=]. + [=traversable navigable's=] [=traversable navigable/captured ad auction signals headers=]. 1. If |auctionConfig|'s [=auction config/component auctions=] are not [=list/is empty|empty=]: 1. [=Assert=] |topLevelAuctionConfig| is null. 1. Let |pendingComponentAuctions| be |auctionConfig|'s [=auction config/component auctions=]'s @@ -1229,7 +1342,7 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. [=list/For each=] |component| in |auctionConfig|'s [=auction config/component auctions=], [=parallel queue/enqueue steps|enqueue the following steps=] to |queue|: 1. Let |compWinner| be the result of running [=generate and score bids=] with |component|, - |auctionConfig|, |global|, and |settings|. + |auctionConfig|, |global|, and |topLevelOrigin|. 1. If |compWinner| is failure, return failure. 1. If [=recursively wait until configuration input promises resolve=] given |auctionConfig| returns failure, return failure. @@ -1243,7 +1356,7 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. Set |topLevelDirectFromSellerSignalsRetrieved| to true. 1. If |compWinner| is not null, then run [=score and rank a bid=] with |auctionConfig|, |compWinner|, |leadingBidInfo|, |decisionLogicScript|, null, "top-level-auction", null, and - |settings|'s [=environment/top-level origin=]. + |topLevelOrigin|. 1. Decrement |pendingComponentAuctions| by 1. 1. Wait until |pendingComponentAuctions| is 0. 1. If |leadingBidInfo|'s [=leading bid info/leading bid=] is null, return null. @@ -1252,8 +1365,8 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. Set |leadingBidInfo|'s [=leading bid info/component seller=] to |winningComponentConfig|'s [=auction config/seller=]. 1. Let « |topLevelSellerSignals|, unusedTopLevelReportResultBrowserSignals » be the result of - running [=report result=] with |leadingBidInfo|, |topLevelDirectFromSellerSignalsForSeller| and - |winningComponentConfig|. + running [=report result=] with |leadingBidInfo|, |topLevelDirectFromSellerSignalsForSeller|, + |winningComponentConfig|, and |global|. 1. Set |leadingBidInfo|'s [=leading bid info/auction config=] to |winningComponentConfig|. 1. Set |leadingBidInfo|'s [=leading bid info/component seller=] to null. 1. Set |leadingBidInfo|'s [=leading bid info/top level seller=] to |auctionConfig|'s @@ -1270,12 +1383,13 @@ To generate and score bids given an [=auction config=] |auctionConfig |leadingBidInfo|'s [=leading bid info/leading bid=]'s [=generated bid/interest group=]'s [=interest group/owner=]. 1. Let « |sellerSignals|, |reportResultBrowserSignals| » be the result of running - [=report result=] with |leadingBidInfo|, |directFromSellerSignalsForSeller|, and null. + [=report result=] with |leadingBidInfo|, |directFromSellerSignalsForSeller|, null, and |global|. 1. Run [=report win=] with |leadingBidInfo|, |sellerSignals|, |reportResultBrowserSignals|, and |directFromSellerSignalsForBuyer|. 1. Return |leadingBidInfo|'s [=leading bid info/leading bid=]. -1. If [=waiting until configuration input promises resolve=] given |auctionConfig| returns failure, return failure. +1. If [=waiting until configuration input promises resolve=] given |auctionConfig| returns failure, + then return failure. 1. Let |allBuyersExperimentGroupId| be |auctionConfig|'s [=auction config/all buyer experiment group id=]. 1. Let |allBuyersGroupLimit| be |auctionConfig|'s [=auction config/all buyers group limit=]. @@ -1286,8 +1400,8 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. Let |directFromSellerSignalsForSeller| be the result of running [=get direct from seller signals for a seller=] given |directFromSellerSignals|. 1. Let |browserSignals| be a {{BiddingBrowserSignals}}. -1. Let |topLevelHost| be the result of running the host serializer on [=this=]'s - [=relevant settings object=]'s [=environment/top-level origin=]'s [=origin/host=]. +1. Let |topLevelHost| be the result of running the host serializer on + |topLevelOrigin|'s [=origin/host=]. 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/topWindowHostname}}"] to |topLevelHost|. 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/seller}}"] to the [=serialization of an origin|serialization=] of |auctionConfig|'s [=auction config/seller=]. @@ -1307,10 +1421,16 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. [=list/For each=] |additionalBid| of |additionalBids|, run the following steps [=in parallel=]: 1. [=Score and rank a bid=] with |auctionConfig|, |additionalBid|, |leadingBidInfo|, |decisionLogicScript|, null, |auctionLevel|, |componentAuctionExpectedCurrency|, and - |settings|'s [=environment/top-level origin=]. + |topLevelOrigin|. 1. Decrement |pendingAdditionalBids| by 1. 1. [=map/For each=] |buyer| → |perBuyerGenerator| of |bidGenerators|, [=parallel queue/enqueue steps|enqueue the following steps=] to |queue|: + 1. Let |perBuyerCumulativeTimeout| be |auctionConfig|'s + [=auction config/all buyers cumulative timeout=]. + 1. If |auctionConfig|'s [=auction config/per buyer cumulative timeouts=] is not null and + [=auction config/per buyer cumulative timeouts=][|buyer|] [=map/exists=], then set + |perBuyerCumulativeTimeout| to |auctionConfig|'s + [=auction config/per buyer cumulative timeouts=][|buyer|]. 1. Let |buyerExperimentGroupId| be |allBuyersExperimentGroupId|. 1. Let |perBuyerExperimentGroupIds| be |auctionConfig|'s [=auction config/per buyer experiment group ids=]. @@ -1344,26 +1464,37 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. [=map/For each=] |signalsUrl| → |perSignalsUrlGenerator| of |perBuyerGenerator|: 1. Let |keys| be a new [=ordered set=]. 1. Let |igNames| be a new [=ordered set=]. + 1. Let |fetchSignalStartTime| be |settings|'s [=environment settings object/current monotonic time=]. + 1. [=map/For each=] joiningOrigin → |groups| of |perSignalsUrlGenerator|: 1. [=list/For each=] |ig| of |groups|: 1. [=set/Append=] |ig|'s [=interest group/trusted bidding signals keys=] to |keys|. 1. [=set/Append=] |ig|'s [=interest group/name=] to |igNames|. 1. Let |biddingSignalsUrl| be the result of [=building trusted bidding signals url=] with - |signalsUrl|, |keys|, |igNames|, |buyerExperimentGroupId|. + |signalsUrl|, |keys|, |igNames|, |buyerExperimentGroupId|, and |topLevelOrigin|. 1. Let « |allTrustedBiddingSignals|, |dataVersion| » be the result of [=fetching trusted signals=] with |biddingSignalsUrl| and true. 1. If |dataVersion| is not null, then [=map/set=] |browserSignals|["{{BiddingBrowserSignals/dataVersion}}"] to |dataVersion|. + 1. Let |fetchSignalDuration| be the [=duration from=] |fetchSignalStartTime| to |settings|'s + [=environment settings object/current monotonic time=], in milliseconds. + 1. If |perBuyerCumulativeTimeout| is not null: + 1. Decrement |perBuyerCumulativeTimeout| by |fetchSignalDuration|. + 1. If |perBuyerCumulativeTimeout| is negative, then [=iteration/break=]; 1. [=map/For each=] joiningOrigin → |groups| of |perSignalsUrlGenerator|: 1. [=list/For each=] |ig| of |groups|: 1. If |ig|'s [=interest group/bidding url=] is null, [=iteration/continue=]. - 1. Let |directFromSellerSignalsForBuyer| be the result of running - [=get direct from seller signals for a buyer=] with |directFromSellerSignals|, and - |ig|'s [=interest group/owner=]. - 1. Let |generatedBid| be the result of [=generate a bid=] given - |allTrustedBiddingSignals|, |auctionSignals|, a [=map/clone=] of |browserSignals|, - |perBuyerSignals|, |directFromSellerSignalsForBuyer|, |perBuyerTimeout|, |expectedCurrency|, - |ig|, and |auctionStartTime|. + 1. If |perBuyerCumulativeTimeout| is not null and is less than |perBuyerTimeout|, then set + |perBuyerTimeout| to |perBuyerCumulativeTimeout|. + 1. Let |generateBidStartTime| be |settings|'s + [=environment settings object/current monotonic time=]. + 1. Let |generatedBid| be the result of [=generate a bid=] given |allTrustedBiddingSignals|, + |auctionSignals|, a [=map/clone=] of |browserSignals|, |perBuyerSignals|, + |perBuyerTimeout|, |expectedCurrency|, |ig|, and |auctionStartTime|. + 1. Let |generateBidDuration| be the [=duration from=] |generateBidStartTime| to |settings|'s + [=environment settings object/current monotonic time=], in milliseconds. + 1. If |perBuyerCumulativeTimeout| is not null, decrement |perBuyerCumulativeTimeout| by + |generateBidDuration|. 1. If |generatedBid| is failure, [=iteration/continue=]. 1. If [=query generated bid k-anonymity count=] given |generatedBid| returns false: @@ -1391,24 +1522,31 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. If [=query component ad k-anonymity count=] given |adComponent|'s [=interest group ad/render url=] returns true, [=list/append=] |adComponent| to |ig|'s [=interest group/ad components=]. + 1. If |perBuyerCumulativeTimeout| is not null and is less than |perBuyerTimeout|, then set + |perBuyerTimeout| to |perBuyerCumulativeTimeout|. + 1. Let |generateBidStartTime| be |settings|'s + [=environment settings object/current monotonic time=]. 1. Set |generatedBid| to the result of [=generate a bid=] given |allTrustedBiddingSignals|, |auctionSignals|, a [=map/clone=] of |browserSignals|, |perBuyerSignals|, |directFromSellerSignalsForBuyer|, |perBuyerTimeout|, |expectedCurrency|, and |ig|. - 1. Set |ig|'s [=interest group/ads=] to |originalAds|. 1. Set |ig|'s [=interest group/ad components=] to |originalAdComponents|. + 1. Let |generateBidDuration| be the [=duration from=] |generateBidStartTime| to + |settings|'s [=environment settings object/current monotonic time=], in milliseconds. + 1. If |perBuyerCumulativeTimeout| is not null, then decrement |perBuyerCumulativeTimeout| + by |generateBidDuration|. 1. If |generatedBid| is failure, [=iteration/continue=]. 1. [=list/Insert=] |generatedBid|'s [=generated bid/interest group=] in |bidIgs|. 1. [=Score and rank a bid=] with |auctionConfig|, |generatedBid|, |leadingBidInfo|, |decisionLogicScript|, |directFromSellerSignalsForSeller|, |dataVersion|, |auctionLevel|, - |componentAuctionExpectedCurrency|, and |settings|'s [=environment/top-level origin=]. + |componentAuctionExpectedCurrency|, and |topLevelOrigin|. 1. Decrement |pendingBuyers| by 1. 1. Wait until both |pendingBuyers| and |pendingAdditionalBids| are 0. 1. If |leadingBidInfo|'s [=leading bid info/leading bid=] is null, return null. 1. If |topLevelAuctionConfig| is null: 1. Let « |sellerSignals|, |reportResultBrowserSignals| » be the result of running - [=report result=] with |leadingBidInfo|, |directFromSellerSignalsForSeller|, and null. + [=report result=] with |leadingBidInfo|, |directFromSellerSignalsForSeller|, null, and |global|. 1. Let |directFromSellerSignalsForWinner| be the result of running [=get direct from seller signals for a buyer=] with |directFromSellerSignals|, and |leadingBidInfo|'s [=leading bid info/leading bid=]'s [=generated bid/interest group=]'s @@ -1478,7 +1616,7 @@ To score and rank a bid given an [=auction config=] |auctionConfig|, {{DirectFromSellerSignalsForSeller}} |directFromSellerSignalsForSeller|, an {{unsigned long}}-or-null |biddingDataVersion|, an enum |auctionLevel|, which is "single-level-auction", "top-level-auction", or "component-auction", a [=currency tag=] |componentAuctionExpectedCurrency|, and an [=origin=] -|topWindowOrigin|: +|topLevelOrigin|: 1. Let |renderURL| be [=URL serializer|serialized=] |generatedBid|'s [=generated bid/ad descriptor=]'s [=ad descriptor/url=]. @@ -1490,7 +1628,7 @@ or "component-auction", a [=currency tag=] |componentAuctionExpectedCurrency|, a to |adComponentRenderURLs|. 1. Let |fullSignalsUrl| be the result of [=building trusted scoring signals url=] with |auctionConfig|'s [=auction config/trusted scoring signals url=], «|renderURL|», |adComponentRenderURLs|, - |auctionConfig|'s [=auction config/seller experiment group id=], and |topWindowOrigin|. + |auctionConfig|'s [=auction config/seller experiment group id=], and |topLevelOrigin|. Implementations may batch requests by collecting render URLs and ad component render URLs from multiple invocations of [=score and rank a bid=] and passing them all to a single invocation @@ -1521,7 +1659,7 @@ or "component-auction", a [=currency tag=] |componentAuctionExpectedCurrency|, a 1. Let |browserSignals| be a {{ScoringBrowserSignals}} with the following fields:
{{ScoringBrowserSignals/topWindowHostname}} -
The result of running the host serializer on |topWindowOrigin|'s [=origin/host=] +
The result of running the host serializer on |topLevelOrigin|'s [=origin/host=]
{{ScoringBrowserSignals/interestGroupOwner}}
[=serialization of an origin|Serialized=] |owner|
{{ScoringBrowserSignals/renderURL}} @@ -1800,6 +1938,9 @@ To encode trusted signals keys given an [=ordered set=] of [=strings= 1. Let |keysStr| be the result of [=string/concatenating=] |keys| with separator set to ",". 1. [=list/Append=] the result of [=string/UTF-8 percent-encoding=] |keysStr| using [=component percent-encode set=] to |list|. + + Issue: The Chrome implementation encodes 0x20 (SP) to U+002B (+), while [=string/UTF-8 percent-encoding=] + encodes it to "%20". 1. Return |list|.
@@ -1807,23 +1948,32 @@ To encode trusted signals keys given an [=ordered set=] of [=strings=
To build trusted bidding signals url given a [=URL=] |signalsUrl|, an [=ordered set=] of -[=strings=] |keys|, an [=ordered set=] of [=strings=] |igNames|, and an {{unsigned short}}-or-null -|experimentGroupId|: +[=strings=] |keys|, an [=ordered set=] of [=strings=] |igNames|, an {{unsigned short}}-or-null +|experimentGroupId|, and an [=origin=] |topLevelOrigin|: 1. Let |queryParamsList| be a new empty [=list=]. + + Note: These steps create a [=url/query=] of the form "`&=`". + E.g., "`hostname=publisher1.com&keys=key1,key2&interestGroupNames=ad+platform,name2&experimentGroupId=1234`". +

These steps don't use the [=urlencoded serializer|application/x-www-form-urlencoded + serializer=] to construct the query string because it repeats a key if it has multiple values + instead of a comma-demilited list (e.g., "`keys=key1&keys=key2`", instead of + "`keys=key1,key2`"), and it also uses a different percent encode set from the Chrome + implementation. + 1. [=list/Append=] "hostname=" to |queryParamsList|. -1. [=list/Append=] the result of [=string/UTF-8 percent-encoding=] [=this=]'s - [=relevant settings object=]'s [=environment/top-level origin=] using - [=component percent-encode set=] to |queryParamsList|. +1. [=list/Append=] the result of [=string/UTF-8 percent-encoding=] the + [=serialization of an origin|serialized=] |topLevelOrigin| using [=component percent-encode set=] + to |queryParamsList|. 1. If |keys| is not [=set/is empty|empty=]: - 1. [=list/Append=] "&keys=" to |queryParamsList|. + 1. [=list/Append=] "`&keys=`" to |queryParamsList|. 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with |keys|. 1. If |igNames| is not [=set/is empty|empty=]: - 1. [=list/Append=] "&interestGroupNames=" to |queryParamsList|. + 1. [=list/Append=] "`&interestGroupNames=`" to |queryParamsList|. 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with |igNames|. 1. If |experimentGroupId| is not null: - 1. [=list/Append=] "&experimentGroupId=" to |queryParamsList|. + 1. [=list/Append=] "`&experimentGroupId=`" to |queryParamsList|. 1. [=list/Append=] [=serialize an integer|serialized=] |experimentGroupId| to |queryParamsList|. 1. Let |fullSignalsUrl| be |signalsUrl|. 1. Set |fullSignalsUrl|'s [=url/query=] to the result of [=string/concatenating=] |queryParamsList|. @@ -1835,24 +1985,24 @@ To build trusted bidding signals url given a [=URL=] |signalsUrl|, an To build trusted scoring signals url given a [=URL=] |signalsUrl|, a [=list=] of [=strings=] |renderURLs|, an [=ordered set=] of [=strings=] |adComponentRenderURLs|, an -{{unsigned short}} |experimentGroupId|, and an [=origin=] |topWindowOrigin|: +{{unsigned short}} |experimentGroupId|, and an [=origin=] |topLevelOrigin|: Note: When trusted scoring signals fetches are not batched, |renderURLs|'s [=list/size=] is 1. 1. Let |queryParamsList| be a new empty [=list=]. 1. [=list/Append=] "hostname=" to |queryParamsList|. -1. [=list/Append=] the result of [=string/UTF-8 percent-encoding=] |topWindowOrigin| using +1. [=list/Append=] the result of [=string/UTF-8 percent-encoding=] |topLevelOrigin| using [=component percent-encode set=] to |queryParamsList|. 1. If |renderURLs| is not [=set/is empty|empty=]: - 1. [=list/Append=] "&renderURLs=" to |queryParamsList|. + 1. [=list/Append=] "`&renderURLs=`" to |queryParamsList|. 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with |renderURLs|. 1. If |adComponentRenderURLs| is not [=set/is empty|empty=]: - 1. [=list/Append=] "&adComponentRenderURLs=" to |queryParamsList|. + 1. [=list/Append=] "`&adComponentRenderURLs=`" to |queryParamsList|. 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with |adComponentRenderURLs|. 1. If |experimentGroupId| is not null: - 1. [=list/Append=] "&experimentGroupId=" to |queryParamsList|. + 1. [=list/Append=] "`&experimentGroupId=`" to |queryParamsList|. 1. [=list/Append=] [=serialize an integer|serialized=] |experimentGroupId| to |queryParamsList|. 1. Set |signalsUrl|'s [=url/query=] to the result of [=string/concatenating=] |queryParamsList|. 1. return |signalsUrl|. @@ -1929,8 +2079,8 @@ To get direct from seller signals for a buyer given a
To report result given a [=leading bid info=] |leadingBidInfo|, a -[=direct from seller signals=]-or-null |directFromSellerSignals|, and an [=auction config=]-or-null -|winningComponentConfig|: +[=direct from seller signals=]-or-null |directFromSellerSignals|, an [=auction config=]-or-null +|winningComponentConfig|, and a [=global object=] |global|: 1. Let |config| be |leadingBidInfo|'s [=leading bid info/auction config=]. 1. Let |bidCurrency| be null. 1. If |winningComponentConfig| is not null: @@ -1960,8 +2110,8 @@ To report result given a [=leading bid info=] |leadingBidInfo|, a 1. Let |browserSignals| be a {{ReportResultBrowserSignals}} with the following fields:
{{topWindowHostname}} -
The result of running the host serializer on [=this=]'s - [=relevant settings object=]'s [=environment/top-level origin=]'s [=origin/host=]. +
The result of running the host serializer on |global|'s + [=environment/top-level origin=]'s [=origin/host=].
{{interestGroupOwner}}
[=serialization of an origin|Serialized=] |winner|'s [=generated bid/interest group=]'s [=interest group/owner=]. @@ -2127,9 +2277,8 @@ The createAuctionNonce() method steps are: * ...which gives browsers the freedom to generate this UUID in another process, and asynchronously send it back to the main thread at an arbitrary future time.
- 1. Let |global| be [=this=]'s [=relevant global object=]. - 1. [=Queue a global task=] on [=DOM manipulation task source=], given |global|, to [=resolve=] - |p| with |nonce|. + 1. [=Queue a global task=] on [=DOM manipulation task source=], given [=this=]'s + [=relevant global object=], to [=resolve=] |p| with |nonce|. 1. Return |p|.
@@ -2146,7 +2295,7 @@ the [=additional bid=] to compete against other bids in a Protected Audience [=a

Each additional bid is expressed using the following JSON data structure:

-
+  
   const additionalBid = {
     "bid": {
       "ad": 'ad-metadata',
@@ -2473,7 +2622,7 @@ identified ahead of time in the [=additional bid=]'s `joiningOrigin` field. Any
 
 

Use `negativeInterestGroup` in additional bid's JSON:

-
+  
   const additionalBid = {
     ...
     "negativeInterestGroup": "example_advertiser_negative_interest_group",
@@ -2481,7 +2630,7 @@ identified ahead of time in the [=additional bid=]'s `joiningOrigin` field. Any
   }
   

Use `negativeInterestGroups` in additional bid's JSON:

-
+  
   const additionalBid = {
     ...
     "negativeInterestGroups": {
@@ -2745,6 +2894,10 @@ of the following global objects:
     1. Let |realm| be the result of [=creating a new script runner realm=] given
       {{InterestGroupBiddingScriptRunnerGlobalScope}}.
     1. Let |global| be |realm|'s [=realm/global object=].
+    1. Let |settings| be |realm|'s [=realm/settings object=].
+
+      Issue: WICG/turtledove#676 needs
+      to be fixed in order to get |realm|'s [=realm/settings object=].
     1. Set |global|'s
       [=InterestGroupBiddingScriptRunnerGlobalScope/group has ad components=] to true if |ig|'s
       [=interest group/ad components=] is not null, or false otherwise.
@@ -2764,11 +2917,12 @@ of the following global objects:
     1. Let |browserSignalsJS| be |browserSignals| [=converted to ECMAScript values=].
     1. Let |directFromSellerSignalsJs| be |directFromSellerSignalsForBuyer|
       [=converted to ECMAScript values=].
-    1. Let |startTime| be the [=current wall time=].
+    1. Let |startTime| be |settings|'s [=environment settings object/current monotonic time=].
     1. Let |result| be the result of [=evaluating a script=] with |realm|, |script|, "`generateBid`",
       « |igJS|, |auctionSignalsJS|, |perBuyerSignalsJS|, |trustedBiddingSignalsJS|, |browserSignalsJS|,
       |directFromSellerSignalsJs| », and |timeout|.
-    1. Let |duration| be the [=current wall time=] minus |startTime| in milliseconds.
+    1. Let |duration| be |settings|'s [=environment settings object/current monotonic time=] minus
+      |startTime| in milliseconds.
     1. If |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/priority=] is not null and not failure:
       1. Set |ig|'s [=interest group/priority=] to |global|'s
         [=InterestGroupBiddingScriptRunnerGlobalScope/priority=].
@@ -2848,13 +3002,16 @@ of the following global objects:
 
 
To evaluate a script with a [=ECMAScript/realm=] |realm|, [=string=] |script|, [=string=] - |functionName|, a [=list=] |arguments|, and an integer millisecond duration |timeout|, run these steps. + |functionName|, a [=list=] |arguments|, and an integer millisecond [=duration=] |timeout|, run these steps. They return a [=ECMAScript/Completion Record=], which is either an [=ECMAScript/abrupt completion=] (in the case of a parse failure or execution error), or a [=ECMAScript/normal completion=] populated with the [=ECMAScript/ECMAScript language value=] result of invoking |functionName|. 1. [=Assert=] that these steps are running [=in parallel=]. + 1. If |timeout| ≤ 0, [=immediately=] interrupt the execution and set |finalCompletion| to a + new [=ECMAScript/throw completion=] given null. + 1. Let |global| be |realm|'s [=realm/global object=], and run these steps in |realm|'s [=realm/agent=]: 1. Let |result| be [$ParseScript$](|script|, |realm|, `empty`). @@ -3025,7 +3182,7 @@ To convert GenerateBidOutput to generated bid given a {{GenerateBidOu [=generated bid/ad cost=] to |generateBidOutput|["{{GenerateBidOutput/adCost}}"]. 1. If |generateBidOutput|["{{GenerateBidOutput/modelingSignals}}"] [=map/exists=]: 1. Let |modelingSignals| be |generateBidOutput|["{{GenerateBidOutput/modelingSignals}}"]. - 1. If |modelingSignals| ≥ 0 and |modelingSignals| < 4096, then set |bid|'s + 1. If |modelingSignals| ≥ 0 and |modelingSignals| < 4096, then set |bid|'s [=generated bid/modeling signals=] to the result of [=converted to an IDL value|converting=] the ECMAScript value represented by |modelingSignals| to an {{unsigned short}}. 1. Return |bid|. @@ -3097,33 +3254,28 @@ To convert GenerateBidOutput to generated bid given a {{GenerateBidOu The setBid(|generateBidOutput|) method steps are: - 1. Set [=this=]'s [=relevant global object=]'s [=InterestGroupBiddingScriptRunnerGlobalScope/bid=] - to null. - 1. Let |ig| be [=this=]'s [=relevant global object=]'s - [=InterestGroupBiddingScriptRunnerGlobalScope/interest group=]. - 1. Let |expectedCurrency| be [=this=]'s [=relevant global object=]'s + 1. Let |global| be [=this=]'s [=relevant global object=]. + 1. Set |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/bid=] to null. + 1. Let |ig| be |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/interest group=]. + 1. Let |expectedCurrency| be |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/expected currency=]. 1. Let |bidToSet| be the result of [=converting GenerateBidOutput to generated bid=] with - |generateBidOutput|, |ig|, |expectedCurrency|, [=this=]'s [=relevant global object=]'s - [=InterestGroupBiddingScriptRunnerGlobalScope/is component auction=], and [=this=]'s - [=relevant global object=]'s + |generateBidOutput|, |ig|, |expectedCurrency|, |global|'s + [=InterestGroupBiddingScriptRunnerGlobalScope/is component auction=], and |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/group has ad components=]. 1. If |bidToSet| is failure, [=exception/throw=] a {{TypeError}}. - 1. Set [=this=]'s [=relevant global object=]'s [=InterestGroupBiddingScriptRunnerGlobalScope/bid=] - to |bidToSet|. + 1. Set |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/bid=] to |bidToSet|.
The setPriority(|priority|) method steps are: - 1. If [=this=]'s [=relevant global object=]'s - [=InterestGroupBiddingScriptRunnerGlobalScope/priority=] is not null: - 1. Set [=this=]'s [=relevant global object=]'s - [=InterestGroupBiddingScriptRunnerGlobalScope/priority=] to failure. - 1. [=exception/Throw=] a {{TypeError}}. - 1. Set [=this=]'s [=relevant global object=]'s - [=InterestGroupBiddingScriptRunnerGlobalScope/priority=] to |priority|. + 1. Let |global| be [=this=]'s [=relevant global object=]. + 1. If |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/priority=] is not null, then set + |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/priority=] to failure, and + [=exception/throw=] a {{TypeError}}. + 1. Set |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/priority=] to |priority|.
@@ -3181,21 +3333,20 @@ Each {{InterestGroupReportingScriptRunnerGlobalScope}} has a The sendReportTo(|url|) method steps are: - 1. If [=this=]'s [=relevant global object=]'s - [=InterestGroupReportingScriptRunnerGlobalScope/report url=] is not null, then Set [=this=]'s - [=relevant global object=]'s [=InterestGroupReportingScriptRunnerGlobalScope/report url=] to - failure, and [=exception/Throw=] a {{TypeError}}. + 1. Let |global| be [=this=]'s [=relevant global object=]. + 1. If |global|'s [=InterestGroupReportingScriptRunnerGlobalScope/report url=] is not null, then + set |global|'s [=InterestGroupReportingScriptRunnerGlobalScope/report url=] to failure, and + [=exception/Throw=] a {{TypeError}}. 1. Let |parsedUrl| be the result of running the [=URL parser=] on |url|. - 1. If |parsedUrl| is failure, or |parsedUrl|'s [=url/scheme=] is not "`https`", set [=this=]'s - [=relevant global object=]'s [=InterestGroupReportingScriptRunnerGlobalScope/report url=] to - failure, and [=exception/Throw=] a {{TypeError}}. + 1. If |parsedUrl| is failure, or |parsedUrl|'s [=url/scheme=] is not "`https`", set |global|'s + [=InterestGroupReportingScriptRunnerGlobalScope/report url=] to failure, and [=exception/throw=] + a {{TypeError}}. 1. Optionally, return. Note: This [=implementation-defined=] condition is intended to allow [=user agents=] to decline for a number of reasons, for example the |parsedUrl|'s [=site=] not being enrolled. - 1. Set [=this=]'s [=relevant global object=]'s - [=InterestGroupReportingScriptRunnerGlobalScope/report url=] to |parsedUrl|. + 1. Set |global|'s [=InterestGroupReportingScriptRunnerGlobalScope/report url=] to |parsedUrl|.
@@ -3331,7 +3482,7 @@ The updateAdInterestGroups() method steps are:
"`executionMode`"
- 1. If |value| is "`compatibility`" or "`group-by-origin`", + 1. If |value| is "`compatibility`", "`frozen-context`", or "`group-by-origin`", set |ig|'s [=interest group/execution mode=] to |value|. 1. Otherwise, jump to the step labeled Abort update. @@ -3377,6 +3528,15 @@ The updateAdInterestGroups() method steps are: set |ig|'s [=interest group/trusted bidding signals keys=] to |value|. 1. Otherwise, jump to the step labeled Abort update. +
"`userBiddingSignals`" +
+ 1. Set |ig|'s [=interest group/user bidding signals=] to the result of [=serialize an Infra + value to JSON bytes=] given |value|. + 1. Otherwise, jump to the step labeled Abort update. + + Issue: Serializing an Infra value to JSON bytes expects to be called within a valid ES realm. See + infra/625 +
"`ads`"
"`adComponents`"
@@ -3623,19 +3783,50 @@ prevents a leak of the user's ad interest group membership to the server.
-# Handling Direct from Seller Signals # {#handling-direct-from-seller-signals} +# Fetch Patch for Auction Headers # {#fetch-patch-for-auction-headers} -This section specifies a manner by which signals may be provided to auctions such that the signals -are only used within their intended auction. +This section specifies a manner by which some data, including [=additional bids=] and +[=direct from seller signals=], may be provided to auctions such that the data is only used within +their intended auction. Any {{Document}} in a [=traversable navigable=] may run a Protected Audience auction (with {{Window/navigator}}.{{Navigator/runAdAuction()}}) whose worklet functions receive signal objects -derived from JSON from an [:Ad-Auction-Signals:] header captured by a -{{WindowOrWorkerGlobalScope/fetch()}} call (using the {{RequestInit/adAuctionHeaders}} option) -initiated by any *other* {{Document}} in the *same* [=traversable navigable=]. +derived from JSON from an [:Ad-Auction-Signals:] header, or [=additional bids=] derived from an +[:Ad-Auction-Additional-Bid:] header, captured by a {{WindowOrWorkerGlobalScope/fetch()}} call +(using the {{RequestInit/adAuctionHeaders}} option) initiated by any *other* {{Document}} in the +*same* [=traversable navigable=], or from an +iframe navigation +request (using the <{iframe/adauctionheaders}> +content attribute on the <{iframe}> element). + +
+Modify [[FETCH]]'s [[FETCH#infrastructure]] to add a new section called "Per Traversable Navigable +Structures", with the following content: + +Each [=traversable navigable=] has a captured ad auction signals +headers, which is a [=map=] whose [=map/keys=] are [=direct from seller signals keys=] and +whose [=map/values=] are [=direct from seller signals=]. + +NOTE: This is only captured during a [=request=] whose [=request/initiator type=] is `"fetch"`, made +with the {{RequestInit/adAuctionHeaders}} option set to `true`, or during an +iframe navigation +request with the <{iframe/adauctionheaders}> +content attribute set to `true`, as described in the +[:Ad-Auction-Signals:] header description. + +Each [=traversable navigable=] has a captured ad auction additional +bids headers, which is a [=map=] whose [=map/keys=] are [=auction nonces=] and whose +[=map/values=] are [=strings=]. + +NOTE: This is only captured during a [=request=] whose [=request/initiator type=] is `"fetch"`, made +with the {{RequestInit/adAuctionHeaders}} option set to `true`, or during an +iframe navigation +request with the <{iframe/adauctionheaders}> content attribute +set to `true`, as described in the [:Ad-Auction-Additional-Bid:] header description. +
-Modify the definition of a [=request=]: +Modify the definition of a [=request=]: A [=request=] has an associated boolean capture-ad-auction-headers. Unless stated otherwise it is false. @@ -3661,60 +3852,38 @@ step "Set [=this=]'s [=Request/request=] to |request|":
-
-The following step will be added to the [=HTTP-network-or-cache fetch=] algorithm, before step -"Modify |httpRequest|'s [=request/header list=] per HTTP. ...": - -1. If [=request=]'s [=request/capture-ad-auction-headers=] is true, then [=header list/set a - structured field value=] given «[:Sec-Ad-Auction-Fetch:], the [=structured header/boolean=] `?1`» - in |httpRequest|'s [=header list=]. +
+Modify the <{iframe}> element to add a +adauctionheaders content attribute. +The IDL attribute {{HTMLIFrameElement/adAuctionHeaders}} [=reflects=] +the <{iframe/adauctionheaders}> content attribute. +
+partial interface HTMLIFrameElement {
+  [CEReactions] attribute boolean adAuctionHeaders;
+};
+
-
-Modify [[FETCH]]'s [[FETCH#infrastructure]] to add a new section called "Per Traversable Navigable -Structures", with the following content: - -

Direct from seller signals key

-A direct from seller signals key is a [=struct=] with the following [=struct/items=]: - -NOTE: This is only captured during a [=request=] whose [=request/initiator type=] is `"fetch"`, made -with the {{RequestInit/adAuctionHeaders}} option set to `true`, as described in the -[:Ad-Auction-Signals:] header description. - -
-: seller -:: An [=origin=]. Matches the origin that served the captured [:Ad-Auction-Signals:] header. -: ad slot -:: A [=string=]. Matches the `adSlot` key of the JSON dictionaries in the top-level array of the - [:Ad-Auction-Signals:] value. - -
+
+The following step will be added to the +create navigation params by fetching steps +after step "Let |request| be a new [=request=], with ...": -

Direct from seller signals

-A direct from seller signals is a [=struct=] with the following [=struct/items=]: +1. If navigable's [=navigable/container=] is an <{iframe}> element, + and if it has a <{iframe/adauctionheaders}> content attribute, + then set |request|'s [=request/capture-ad-auction-headers=] to true. -NOTE: This is only captured during a [=request=] whose [=request/initiator type=] is `"fetch"`, made -with the {{RequestInit/adAuctionHeaders}} option set to `true`, as described in the -[:Ad-Auction-Signals:] header description. +
-
-: auction signals -:: Null or a [=string=]. - Opaque JSON data passed to both buyers' and the seller's [=script runners=]. -: seller signals -:: Null or a [=string=]. - Opaque JSON data passed to the seller's [=script runner=]. -: per buyer signals -:: A [=map=] whose [=map/keys=] are [=origins=] and whose [=map/values=] are [=strings=]. - [=map/Keys=] are buyers and must be valid HTTPS origins. [=map/Values=] are opaque JSON data - passed to corresponding buyer's [=script runner=]. +
+The following step will be added to the [=HTTP-network-or-cache fetch=] algorithm, before step +"Modify |httpRequest|'s [=request/header list=] per HTTP. ...": -
+1. If [=request=]'s [=request/capture-ad-auction-headers=] is true, then [=header list/set a + structured field value=] given «[:Sec-Ad-Auction-Fetch:], the [=structured header/boolean=] `?1`» + in |httpRequest|'s [=header list=]. -Each [=traversable navigable=] has a captured ad auction headers -, which is a [=map=] whose [=map/keys=] are [=direct from seller signals keys=] and whose -[=map/values=] are [=direct from seller signals=].
@@ -3726,9 +3895,13 @@ request header The \`Sec-Ad-Auction-Fetch\` request header is an optional [=structured header=] with of type [=structured header/boolean=]. [:Sec-Ad-Auction-Fetch:] will only be set on a [=request=] whose [=request/initiator type=] is `"fetch"`, made with the -{{RequestInit/adAuctionHeaders}} option set to `true`. If [:Sec-Ad-Auction-Fetch:] is equal to `?1`, -the user agent will remove any [:Ad-Auction-Signals:] from the returned [=response=] -- the -[:Ad-Auction-Signals:] value will instead only be used in Protected Audiences auctions. +{{RequestInit/adAuctionHeaders}} option set to `true`, or on an +iframe navigation +request with the <{iframe/adauctionheaders}> content attribute +set to `true`. If [:Sec-Ad-Auction-Fetch:] is equal to `?1`, +the user agent will remove any [:Ad-Auction-Signals:] or [:Ad-Auction-Additional-Bid:] from the +returned [=response=] -- the [:Ad-Auction-Signals:] or [:Ad-Auction-Additional-Bid:] value will +instead only be used in Protected Audiences auctions.

The \`Ad-Auction-Signals\` HTTP response header

@@ -3742,15 +3915,17 @@ HTTP response header. The \`Ad-Auction-Additional-Bid\` response header provides value of a string in the format of `:`, which -corresponds to a single additional bid. The response may include more than one additional bid by -specifying multiple instances of the [:Ad-Auction-Additional-Bid:] response header. +corresponds to a single [=additional bid=]. The response may include more than one [=additional bid=] +by specifying multiple instances of the [:Ad-Auction-Additional-Bid:] response header.
-
-The following step will be added to the [=HTTP fetch=] algorithm, immediately under the step "If +
+The following steps will be added to the [=HTTP fetch=] algorithm, immediately under the step "If internalResponse’s [=status=] is a [=redirect status=]:" -1. [=header list/Delete=] "[:Ad-Auction-Signals:]" from response's +1. [=header list/Delete=] "[:Ad-Auction-Signals:]" from |response|'s + [=response/header list=]. +1. [=header list/Delete=] "[:Ad-Auction-Additional-Bid:]" from |response|'s [=response/header list=].
@@ -3761,40 +3936,70 @@ The following step will be added to the [=HTTP fetch=] algorithm, before step 1. If |response| is not null, |response|'s [=status=] is not a [=redirect status=], |fetchParams|'s [=fetch params/task destination=] is a [=global object=] that's a {{Window}} object, and - |request|'s [=request/capture-ad-auction-headers=] is `true`, then run [=update captured headers=] - with |fetchParams|'s [=fetch params/task destination=]'s [=associated Document's=] [=node - navigable's=] [=traversable navigable's=] [=traversable navigable/captured ad auction headers=], - |response|'s [=response/header list=], and |request|'s [=request/URL=]'s [=url/origin=]. + |request|'s [=request/capture-ad-auction-headers=] is `true`: + 1. Let |navigable| be |fetchParams|'s [=fetch params/task destination=]'s [=associated Document=]'s + [=node navigable=]'s [=traversable navigable=]. + 1. Run [=update captured headers=] with |navigable|'s + [=traversable navigable/captured ad auction signals headers=], |navigable|'s + [=traversable navigable/captured ad auction additional bids headers=], |response|'s + [=response/header list=], and |request|'s [=request/URL=]'s [=url/origin=].
The following algorithm will be added to the [[FETCH#fetching]] section: -

Update captured headers

- To update captured headers with a [=traversable - navigable/captured ad auction headers=] |storedHeaders|, [=header list=] |responseHeaders|, and - [=origin=] |requestOrigin|: + navigable/captured ad auction signals headers=] |storedSignalsHeaders|, + [=traversable navigable/captured ad auction additional bids headers=] |storedAdditionalBidsHeaders|, + [=header list=] |responseHeaders|, and [=origin=] |requestOrigin|: 1. Let |adAuctionSignals| be the result of [=header list/getting=] [:Ad-Auction-Signals:] from |responseHeaders|. - 1. If |adAuctionSignals| is null, return. - 1. [=header list/Delete=] "[:Ad-Auction-Signals:]" from |responseHeaders|. + 1. If |adAuctionSignals| is not null: + 1. [=header list/Delete=] "[:Ad-Auction-Signals:]" from |responseHeaders|. + + NOTE: This step prevents the header value from being used outside the intended auctions -- + that is, scripts making the {{WindowOrWorkerGlobalScope/fetch()}} request aren't able to load + the header value. + 1. [=Handle ad auction signals header value=] given |adAuctionSignals|, |storedSignalsHeaders| and + |requestOrigin|. + 1. Let |additionalBids| be the result of [=header list/getting, decoding, and splitting=] + [:Ad-Auction-Additional-Bid:] from |responseHeaders|. + 1. If |additionalBids| is not null: + 1. [=header list/Delete=] "[:Ad-Auction-Additional-Bid:]" from |responseHeaders|. + + NOTE: This step prevents the header value from being used outside the intended auctions -- + that is, scripts making the {{WindowOrWorkerGlobalScope/fetch()}} request aren't able to load + the header value. + 1. [=list/For each=] |bid| of |additionalBids|: + 1. Let |nonceAndAdditionalBid| be the result of [=strictly splitting=] |bid| on U+003A (:). + 1. If |nonceAndAdditionalBid|'s [=list/size=] is not 2, then [=iteration/continue=]. + 1. Let |nonce| be |nonceAndAdditionalBid|[0]. + 1. If |nonce|'s [=string/length=] is not 36, then [=iteration/continue=]. + 1. Set |storedAdditionalBidsHeaders|[|nonce|] to |nonceAndAdditionalBid|[1]. + +
+ +
+To handle ad auction signals header value given a [=byte sequence=] |adAuctionSignals|, +[=traversable navigable/captured ad auction signals headers=] |storedSignalsHeaders|, and [=origin=] +|requestOrigin|: - NOTE: This step prevents the header value from being used outside the intended auctions -- - that is, scripts making the {{WindowOrWorkerGlobalScope/fetch()}} request aren't able to load - the header value. 1. Let |parsedSignals| be the result of [=parsing JSON bytes to an Infra value=], given |adAuctionSignals|. - 1. If |parsedSignals| is failure, return. - 1. If |parsedSignals| is not a [=list=], return. - 1. [=list/For each=] |signal| in |parsedSignals|: + 1. If |parsedSignals| is failure or not a [=list=], return. + 1. Let |headerAdSlots| be a new [=ordered set=]. + 1. [=list/For each=] |signal| of |parsedSignals|: 1. If |signal| is not an [=ordered map=], [=iteration/continue=]. 1. If |signal|["`adSlot`"] doesn't exist, [=iteration/continue=]. - 1. Create a new [=direct from seller signals key=] |signalsKey|, with its + 1. If |headerAdSlots| [=set/contains=] |signal|["`adSlot`"], [=iteration/continue=]. Optionally, + [=report a warning to the console=] with a diagnostic error message indicating that a + duplicate [:Ad-Auction-Signals:] `adSlot` dictionary was ignored. + 1. [=set/Append=] |signal|["`adSlot`"] to |headerAdSlots|. + 1. Let |signalsKey| be a new [=direct from seller signals key=], with its [=direct from seller signals key/seller=] set to |requestOrigin| and its [=direct from seller signals key/ad slot=] set to |signal|["`adSlot`"]. - 1. Create a new [=direct from seller signals=] |processedSignals|. + 1. Let |processedSignals| be a new [=direct from seller signals=]. 1. [=map/Remove=] |signal|["`adSlot`"]. 1. [=map/For each=] |key| → |value| of |signal|: 1. Switch on |key|: @@ -3813,17 +4018,15 @@ The following algorithm will be added to the [[FETCH#fetching]] section:
1. If |value| is not an [=ordered map=], [=iteration/continue=]. 1. For each |buyer| → |buyerSignals| of |value|: - 1. Let |buyerOrigin| be the result of [=parsing an https origin=] on |buyer|. If this - [=exception/throws=], [=iteration/continue=]. + 1. Let |buyerOrigin| be the result of [=parsing an https origin=] on |buyer|. + 1. If |buyerOrigin| is failure, [=iteration/continue=]. 1. Let |buyerSignalsString| be the result of [=serializing an Infra value to a JSON string=], given |buyerSignals|. 1. Set |processedSignals|'s [=direct from seller signals/per buyer signals=][|buyerOrigin|] to |buyerSignalsString|. - - 1. Set |storedHeaders|[|signalsKey|] to |processedSignals|. - + 1. Set |storedSignalsHeaders|[|signalsKey|] to |processedSignals|.
@@ -3941,7 +4144,10 @@ dictionary ReportWinBrowserSignals : ReportingBrowserSignals { DOMString buyerReportingId; unsigned short modelingSignals; unsigned long dataVersion; + KAnonStatus kAnonStatus; }; + +enum KAnonStatus { "passedAndEnforced", "passedNotEnforced", "belowThreshold", "notCalculated" };
@@ -3969,6 +4175,17 @@ dictionary ReportWinBrowserSignals : ReportingBrowserSignals {
{{ReportWinBrowserSignals/dataVersion}}
Only set if the Data-Version header was provided in the response headers from the trusted bidding signals server +
{{ReportWinBrowserSignals/kAnonStatus}} +
Indicate the k-anonymity status of the ad with the following {{KAnonStatus}} enums: + * {{KAnonStatus/passedAndEnforced}}: The ad was k-anonymous and k-anonymity was required to win the auction. + * {{KAnonStatus/passedNotEnforced}}: The ad was k-anonymous though k-anonymity was not required to win the auction. + * {{KAnonStatus/belowThreshold}}: The ad was not k-anonymous but k-anonymity was not required to win the auction. + * {{KAnonStatus/notCalculated}}: The browser did not calculate the k-anonymity status of the ad, and k-anonymity was not required to win the auction. + + From a long-term perspective, the status will always be set to `passedAndEnforced` after + k-anonymity is enforced. However, as a temporary solution, current implementations may set + `kAnonStatus` to one of the other three statuses to allow API users to assess the future + impact of enforcing that ads are k-anonymous.
@@ -3985,102 +4202,101 @@ dictionary DirectFromSellerSignalsForSeller { }; -

Interest group

+

Interest group

-An interest group is a [=struct=] with the following [=struct/items=]: +An interest group is a [=struct=] with the following [=struct/items=]:
-: expiry -:: A [=moment=] at which the browser will forget about this interest group. -: owner -:: An [=origin=]. Frames that join interest groups owned by [=interest group/owner=] must either be - served from [=interest group/owner=], or another origin delegated by [=interest group/owner=] (See - [=checking interest group permissions=] for details). The [=origin/scheme=] must be "`https`". -: name -:: A [=string=]. The ([=interest group/owner=], [=interest group/name=]) tuple is a key that - uniquely defines each interest group. -: priority -:: A {{double}}, initially 0.0. Used to select which interest groups participate in an auction - when the number of interest groups are limited by {{AuctionAdConfig/perBuyerGroupLimits}}. - See [=applying interest groups limits to prioritized list=]. -: enable bidding signals prioritization -:: A [=boolean=], initially false. Being true if the interest group's priority should be - calculated using vectors from bidding signals fetch. -: priority vector -:: Null or an [=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are - {{double}}. Its dot product with the {{AuctionAdConfig/perBuyerPrioritySignals}} will be used - in place of [=interest group/priority=], if set. -: priority signals overrides -:: Null or an [=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are - {{double}}. Overrides the {{AuctionAdConfig}}'s corresponding priority signals. -: execution mode -:: "`compatibility`" or "`group-by-origin`". - TODO: Define spec for these execution modes, link to it from here and explain these modes. -: bidding url -:: Null or a [=URL=]. The URL to fetch the buyer's JavaScript from. -

- When non-null, the [=interest group/bidding url=]'s [=origin=] will always be [=same origin=] - with [=interest group/owner=]. -

-: bidding wasm helper url -:: Null or a [=URL=]. Lets the bidder provide computationally-expensive subroutines in WebAssembly, - in addition to JavaScript, to be driven from the JavaScript function provided by - [=interest group/bidding url=]. -

- When non-null, the [=interest group/bidding wasm helper url=]'s [=origin=] will always be - [=same origin=] with [=interest group/owner=]. -

-: update url -:: Null or a [=URL=]. Provides a mechanism for the group's owner to periodically update the - attributes of the interest group. See [[#interest-group-updates]]. Must be null if - [=interest group/additional bid key=] is not null. -

- When non-null, the [=interest group/update url=]'s [=origin=] will always be [=same origin=] - with [=interest group/owner=]. -

-: trusted bidding signals url -:: Null or a [=URL=]. Provide a mechanism for making real-time data available for use at bidding - time. See [=building trusted bidding signals url=]. -

- When non-null, the [=interest group/trusted bidding signals url=]'s [=origin=] will always be - [=same origin=] with [=interest group/owner=]. -

-: trusted bidding signals keys -:: Null or a [=list=] of [=string=]. See [=building trusted bidding signals url=]. -: user bidding signals -:: Null or a [=string=]. Additional metadata that the owner can use during on-device bidding. -: ads -:: Null or a [=list=] of [=interest group ad=]. Contains various ads that the interest group might - show. Must be null if [=interest group/additional bid key=] is not null. -: ad components -:: Null or a [=list=] of [=interest group ad=]. Contains various ad components (or "products") that - can be used to construct ads composed of multiple pieces — a top-level ad template "container" - which includes some slots that can be filled in with specific "products". -: additional bid key -:: Null or a [=byte sequence=] of length 32. Must be null if [=interest group/ads=] or - [=interest group/update url=] is not null. The Ed25519 public key (a 256-bit EdDSA public key) - used to guarantee that this [=interest group=], if used by an additional bid for a negative - targeting, can only be used by its [=interest group/owner=]. -: joining origin -:: An [=origin=]. The top level page origin from where the interest group was joined. -: join counts -:: A [=list=] containing [=tuples=] of the day and per day join count. The day - is calculated based on UTC time. The join count is a count of the number of - times {{Navigator/joinAdInterestGroup()}} was called for this interest group on the - corresponding day. -: join time -:: A [=moment=] at which the browser joined this interest group, updated upon each join and - re-join. -: bid counts -:: A [=list=] containing [=tuples=] of the day and per day bid count. The day - is calculated based on UTC time. The bid count is a count of the number of - times the bid calculated during {{Navigator/runAdAuction()}} was greater than 0. -: previous wins -:: A [=list=] of [=previous wins=]. -: next update after -:: A [=moment=] at which the browser will permit updating this interest group. See - [interest group updates](#interest-group-updates). - + : expiry + :: A [=moment=] at which the browser will forget about this interest group. + : owner + :: An [=origin=]. Frames that join interest groups owned by [=interest group/owner=] must either be + served from [=interest group/owner=], or another origin delegated by [=interest group/owner=] (See + [=checking interest group permissions=] for details). The [=origin/scheme=] must be "`https`". + : name + :: A [=string=]. The ([=interest group/owner=], [=interest group/name=]) tuple is a key that + uniquely defines each interest group. + : priority + :: A {{double}}, initially 0.0. Used to select which interest groups participate in an auction + when the number of interest groups are limited by {{AuctionAdConfig/perBuyerGroupLimits}}. + See [=applying interest groups limits to prioritized list=]. + : enable bidding signals prioritization + :: A [=boolean=], initially false. Being true if the interest group's priority should be + calculated using vectors from bidding signals fetch. + : priority vector + :: Null or an [=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are + {{double}}. Its dot product with the {{AuctionAdConfig/perBuyerPrioritySignals}} will be used + in place of [=interest group/priority=], if set. + : priority signals overrides + :: Null or an [=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are + {{double}}. Overrides the {{AuctionAdConfig}}'s corresponding priority signals. + : execution mode + :: "`compatibility`", "`frozen-context`", or "`group-by-origin`". + TODO: Define spec for these execution modes, link to it from here and explain these modes. + : bidding url + :: Null or a [=URL=]. The URL to fetch the buyer's JavaScript from. +

+ When non-null, the [=interest group/bidding url=]'s [=origin=] will always be [=same origin=] + with [=interest group/owner=]. +

+ : bidding wasm helper url + :: Null or a [=URL=]. Lets the bidder provide computationally-expensive subroutines in WebAssembly, + in addition to JavaScript, to be driven from the JavaScript function provided by + [=interest group/bidding url=]. +

+ When non-null, the [=interest group/bidding wasm helper url=]'s [=origin=] will always be + [=same origin=] with [=interest group/owner=]. +

+ : update url + :: Null or a [=URL=]. Provides a mechanism for the group's owner to periodically update the + attributes of the interest group. See [[#interest-group-updates]]. Must be null if + [=interest group/additional bid key=] is not null. +

+ When non-null, the [=interest group/update url=]'s [=origin=] will always be [=same origin=] + with [=interest group/owner=]. +

+ : trusted bidding signals url + :: Null or a [=URL=]. Provide a mechanism for making real-time data available for use at bidding + time. See [=building trusted bidding signals url=]. +

+ When non-null, the [=interest group/trusted bidding signals url=]'s [=origin=] will always be + [=same origin=] with [=interest group/owner=]. +

+ : trusted bidding signals keys + :: Null or a [=list=] of [=string=]. See [=building trusted bidding signals url=]. + : user bidding signals + :: Null or a [=string=]. Additional metadata that the owner can use during on-device bidding. + : ads + :: Null or a [=list=] of [=interest group ad=]. Contains various ads that the interest group might + show. Must be null if [=interest group/additional bid key=] is not null. + : ad components + :: Null or a [=list=] of [=interest group ad=]. Contains various ad components (or "products") that + can be used to construct ads composed of multiple pieces — a top-level ad template "container" + which includes some slots that can be filled in with specific "products". + : additional bid key + :: Null or a [=byte sequence=] of length 32. Must be null if [=interest group/ads=] or + [=interest group/update url=] is not null. The Ed25519 public key (a 256-bit EdDSA public key) + used to guarantee that this [=interest group=], if used by an additional bid for a negative + targeting, can only be used by its [=interest group/owner=]. + : joining origin + :: An [=origin=]. The top level page origin from where the interest group was joined. + : join counts + :: A [=list=] containing [=tuples=] of the day and per day join count. The day + is calculated based on UTC time. The join count is a count of the number of + times {{Navigator/joinAdInterestGroup()}} was called for this interest group on the + corresponding day. + : join time + :: A [=moment=] at which the browser joined this interest group, updated upon each join and + re-join. + : bid counts + :: A [=list=] containing [=tuples=] of the day and per day bid count. The day + is calculated based on UTC time. The bid count is a count of the number of + times the bid calculated during {{Navigator/runAdAuction()}} was greater than 0. + : previous wins + :: A [=list=] of [=previous wins=]. + : next update after + :: A [=moment=] at which the browser will permit updating this interest group. See + [interest group updates](#interest-group-updates).
A regular interest group is an [=interest group=] whose @@ -4089,32 +4305,39 @@ A regular interest group is an [=interest group=] whose A negative interest group is an [=interest group=] whose [=interest group/additional bid key=] is not null. -

Interest group ad

- -An interest group ad is a [=struct=] with the following [=struct/items=]: +An interest group ad is a [=struct=] with the following [=struct/items=]:
-: render url -:: A [=URL=]. If this ad wins the auction, this URL (or a [=urn uuid=] that maps to this URL) will - be returned by {{Navigator/runAdAuction()}}. This URL is intended to be loaded into an ad - <{iframe}> (or a <{fencedframe}>). -: metadata -:: Null or a [=string=]. Extra arbitary information about this ad, passed to `generateBid()`. -: buyer reporting ID -:: Null or a [=string=]. Will be passed in place of interest group name to [=report win=], subject - to [=k-anonymity=] checks. Only meaningful in [=interest group/ads=], but ignored in - [=interest group/ad components=]. -: buyer and seller reporting ID -:: Null or a [=string=]. Will be passed in place of interest group name or - [=interest group ad/buyer reporting ID=] to [=report win=] and [=report result=], subject to - [=k-anonymity=] checks. Only meaningful in [=interest group/ads=], but ignored in - [=interest group/ad components=]. -: allowed reporting origins -:: Null or a [=list=] of [=origins=]. A list of up to 10 reporting origins that can receive reports - with registered macros. All origins must be HTTPS origins and - enrolled. Only meaningful in - [=interest group/ads=], but ignored in [=interest group/ad components=]. + : render url + :: A [=URL=]. If this ad wins the auction, this URL (or a [=urn uuid=] that maps to this URL) will + be returned by {{Navigator/runAdAuction()}}. This URL is intended to be loaded into an ad + <{iframe}> (or a <{fencedframe}>). + : metadata + :: Null or a [=string=]. Extra arbitary information about this ad, passed to `generateBid()`. + : buyer reporting ID + :: Null or a [=string=]. Will be passed in place of interest group name to [=report win=], subject + to [=k-anonymity=] checks. Only meaningful in [=interest group/ads=], but ignored in + [=interest group/ad components=]. + : buyer and seller reporting ID + :: Null or a [=string=]. Will be passed in place of interest group name or + [=interest group ad/buyer reporting ID=] to [=report win=] and [=report result=], subject to + [=k-anonymity=] checks. Only meaningful in [=interest group/ads=], but ignored in + [=interest group/ad components=]. + : allowed reporting origins + :: Null or a [=list=] of [=origins=]. A list of up to 10 reporting origins that can receive reports + with registered macros. Each origin's [=origin/scheme=] must be "`https`" and each origin must be + enrolled. Only meaningful in + [=interest group/ads=], but ignored in [=interest group/ad components=]. +
+ +A previous win is the [=interest group=]'s auction win history, to allow on-device +frequency capping. +
+ : time + :: A [=moment=]. Approximate time the [=interest group=] won an auction. + : ad json + :: A [=string=]. A JSON serialized object corresponding to the ad that won the auction.

Currency tag

@@ -4145,302 +4368,326 @@ value is used to denote that the currency is unspecified. 1. Return false.
-

Auction config

+

Auction config

-An auction config is a [=struct=] with the following items: +An auction config is a [=struct=] with the following items:
-: seller -:: An [=origin=]. - The origin of the seller running the ad auction. The [=origin/scheme=] must be "https". -: decision logic url -:: A [=URL=]. - The URL to fetch the seller's JavaScript from. -

- The [=auction config/decision logic url=]'s [=origin=] will always be [=same origin=] with - [=auction config/seller=]. -

-: trusted scoring signals url -:: Null or a [=URL=]. - Provide a mechanism for making real-time data (information about a specific [=ad creative=]) available - for use at scoring time, e.g. the results of some ad scanning system. -

- When non-null, the [=auction config/trusted scoring signals url=]'s [=origin=] will always be - [=same origin=] with [=auction config/seller=]. -

-: interest group buyers -:: Null or a [=list=] of [=origins=]. - Owners of interest groups allowed to participate in the auction. Each [=origin's=] [=origin/scheme=] - must be "https". -: auction signals -:: Null, a [=string=], a {{Promise}}, or failure. - Opaque JSON data passed to both sellers' and buyers' [=script runners=]. -: seller signals -:: Null, a [=string=], a {{Promise}}, or failure. - Opaque JSON data passed to the seller's [=script runner=]. -: seller timeout -:: A [=duration=] in milliseconds, initially 50 milliseconds. - Restricts the runtime of the seller's `scoreAd()` script. If scoring does not complete before - the timeout, the bid being scored is not considered further. -: per buyer signals -:: Null, a {{Promise}}, failure, or an [=ordered map=] whose [=map/keys=] are [=origins=] and - whose [=map/values=] are [=strings=]. - [=map/Keys=] are buyers and must be valid HTTPS origins. [=map/Values=] are opaque JSON data - passed to corresponding buyer's [=script runner=]. -: per buyer timeouts -:: Null, a {{Promise}}, failure, or an [=ordered map=] whose [=map/keys=] are [=origins=] and - whose [=map/values=] are [=durations=] in milliseconds. - [=map/Keys=] are buyers and must be valid HTTPS origins. [=map/Values=] restrict the runtime of - corresponding buyer's `generateBid()` script. If the timeout expires, only the bid submitted - via `setBid()` is considered. -: all buyers timeout -:: A [=duration=] in milliseconds, initially 50 milliseconds. - Restricts the `generateBid()` script's runtime for all buyers without a timeout specified in - [=auction config/per buyer timeouts=]. If the timeout expires, only the bid submitted via - `setBid()` is considered. -: per buyer group limits -:: Null or an [=ordered map=] whose [=map/keys=] are [=origins=] and whose [=map/values=] are - {{unsigned short}}s. - [=map/Keys=] are buyers and must be valid HTTPS origins. [=map/Values=] restrict the number of - bidding interest groups for a particular buyer that can participate in an auction. -: all buyers group limit -:: An {{unsigned short}}, initially 65535. - Limit on the number of bidding interest groups for all buyers without a limit specified in - [=auction config/per buyer group limits=]. -: per buyer priority signals -:: Null or an [=ordered map=] whose [=map/keys=] are [=origins=] and whose [=map/values=] are - [=ordered maps=], whose [=map/keys=] are [=strings=] and whose [=map/values=] are {{double}}. - Per-buyer sparse vector whose dot product with [=interest group/priority vector=] is used to - calculate interest group priorities. No signal's key starts with "browserSignals.", which is - reserved for values coming from the browser. -: all buyers priority signals -:: Null or an [=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are - {{double}}. - Merged with [=auction config/per buyer priority signals=] before calculating per-interest group - priorities. In the case both have entries with the same key, the entry in - `per_buyer_priority_signals` takes precedence. No signals key start with "browserSignals.", which - is reserved for values coming from the browser. -: component auctions -:: A [=list=] of [=auction config=]s. - Nested auctions whose results will also participate in a top level auction. Only the top level - [=auction config=] can have component auctions. -: seller experiment group id -:: Null or an {{unsigned short}}, initially null. - Optional identifier for an experiment group to support coordinated experiments with the seller's - trusted server. -: per buyer experiment group ids -:: An [=ordered map=] whose [=map/keys=] are [=origins=] and whose [=map/values=] are - {{unsigned short}}s. - [=map/Keys=] are buyers and must be valid HTTPS origins. [=map/Values=] are identifiers for - experiment groups, to support coordinated experiments with buyers' trusted servers. -: all buyer experiment group id -:: Null or an {{unsigned short}}, initially null. - Optional identifier for an experiment group to support coordinated experiments with buyers' - trusted servers for buyers without a specified experiment group. -: pending promise count -:: An integer, initially 0. The number of things that are pending that are needed to score - everything. It includes waiting for {{Promise}}s [=auction config/auction signals=], - [=auction config/per buyer signals=], [=auction config/per buyer currencies=], - [=auction config/per buyer timeouts=], [=auction config/direct from seller signals header ad slot=], - [=auction config/seller signals=], or {{AuctionAdConfig/additionalBids}} whose {{Promise}}s are not - yet resolved. -: config idl -:: {{AuctionAdConfig}}. -: resolve to config -:: A [=boolean=] or a {{Promise}}, initially false. - Whether the ad should be returned as a {{FencedFrameConfig}}, or otherwise as a [=urn uuid=]. -: seller currency -:: A [=currency tag=]. Specifies the currency bids returned by `scoreAd()` are expected to use, and - which reporting for this auction will agree on. -: per buyer currencies -:: A {{Promise}} or failure or an [=ordered map=] whose [=map/keys=] are [=origins=] and whose - [=map/values=] are [=currency tags=]. Specifies the currency bids returned by `generateBid()` or - `scoreAd()` in component auctions are expected to use. The initial value is an empty map. -: all buyers currency -:: A [=currency tag=]. Specifies the currency bids returned by `generateBid()` or `scoreAd()` in - component auctions are expected to use if [=auction config/per buyer currencies=] does not specify - a particular value. -: direct from seller signals header ad slot -:: Null, a [=string=], a {{Promise}}, or failure. Initially null. -: auction nonce -:: Null or a [=version 4 UUID=], initially null. - A unique identifier associated with this and only this invocation of - {{Window/navigator}}.{{Navigator/runAdAuction()}}. For - multi-seller auctions, this ID is uniquely associated with all {{AuctionAdConfig/componentAuctions}}. - This must come from a prior call to {{Window/navigator}}.{{Navigator/createAuctionNonce()}}. This - is only required for auctions that provide additional bids, and each of those additional bids must - use the same auction nonce to ensure that each of those additional bids was intended for this and - only this auction. -: expects additional bids -:: A [=boolean=] or failure, initially false. - Specifies whether some bids will be provided as signed exchanges. Sets to failure if the - {{AuctionAdConfig/additionalBids}} {{Promise}} is [=rejected=]. - + : seller + :: An [=origin=]. + The origin of the seller running the ad auction. The [=origin/scheme=] must be "`https`". + : decision logic url + :: A [=URL=]. + The URL to fetch the seller's JavaScript from. +

+ The [=auction config/decision logic url=]'s [=origin=] will always be [=same origin=] with + [=auction config/seller=]. +

+ : trusted scoring signals url + :: Null or a [=URL=]. + Provide a mechanism for making real-time data (information about a specific [=ad creative=]) + available for use at [=evaluate a scoring script|scoring=] time, e.g. the results of some ad + scanning system. +

+ When non-null, the [=auction config/trusted scoring signals url=]'s [=origin=] will always be + [=same origin=] with [=auction config/seller=]. +

+ : interest group buyers + :: Null or a [=list=] of [=origins=]. + Owners of interest groups allowed to participate in the auction. Each [=origin's=] + [=origin/scheme=] must be "`https`". + : auction signals + :: Null, a [=string=], a {{Promise}}, or failure. + Opaque JSON data passed to both sellers' and buyers' [=script runners=]. + : requested size + :: Null or an [=ad size=], initially null. + The size of the frame for the ad being selected by the auction. + : seller signals + :: Null, a [=string=], a {{Promise}}, or failure. + Opaque JSON data passed to the seller's [=script runner=]. + : seller timeout + :: A [=duration=] in milliseconds, initially 50 milliseconds. + Restricts the runtime of the seller's `scoreAd()` script. If scoring does not complete before + the timeout, the bid being scored is not considered further. + : per buyer signals + :: Null, a {{Promise}}, failure, or an [=ordered map=] whose [=map/keys=] are [=origins=] and + whose [=map/values=] are [=strings=]. + [=map/Keys=] are buyers whose [=origin/schemes=] must be "`https`". [=map/Values=] are + opaque JSON data passed to corresponding buyer's [=script runner=]. + : per buyer timeouts + :: Null, a {{Promise}}, failure, or an [=ordered map=] whose [=map/keys=] are [=origins=] and + whose [=map/values=] are [=durations=] in milliseconds. + [=map/Keys=] are buyers whose [=origin/schemes=] must be "`https`". [=map/Values=] restrict the + runtime of corresponding buyer's `generateBid()` script. If the timeout expires, only the bid + submitted via `setBid()` is considered. + : all buyers timeout + :: A [=duration=] in milliseconds, initially 50 milliseconds. + Restricts the `generateBid()` script's runtime for all buyers without a timeout specified in + [=auction config/per buyer timeouts=]. If the timeout expires, only the bid submitted via + `setBid()` is considered. + : per buyer cumulative timeouts + :: Null, a {{Promise}}, failure, or an [=ordered map=] whose [=map/keys=] are [=origins=] and + whose [=map/values=] are [=durations=] in milliseconds. + [=map/Keys=] are buyers whose [=origin/schemes=] must be "`https`". [=map/Values=] are collective + timeouts for all interest groups of the buyer represented by the [=map/key=]. Includes the time of + loading scripts and signals, and running the `generateBid()` functions. Once the timer expires, + the affected buyer's interest groups may no longer generate any bids. All bids generated before + the timeout will continue to participate in the auction. + Implementations should attempt, on a best-effort basis, to generate bids for each buyer in + priority order, so lower priority [=interest groups=] are the ones more likely to be timed out. If + {{Promise}}s are passed in to the [=auction config=] for fields that support them, + [=wait until configuration input promises resolve=] before starting the timer. + : all buyers cumulative timeout + :: Null or a [=duration=] in milliseconds, initially null. + Restricts a buyer's cumulative timeout for all buyers without one specified in + [=auction config/per buyer cumulative timeouts=]. + : per buyer group limits + :: Null or an [=ordered map=] whose [=map/keys=] are [=origins=] and whose [=map/values=] are + {{unsigned short}}s. + [=map/Keys=] are buyers whose [=origin/schemes=] must be "`https`". [=map/Values=] restrict the + number of bidding interest groups for a particular buyer that can participate in an auction. + : all buyers group limit + :: An {{unsigned short}}, initially 65535. + Limit on the number of bidding interest groups for all buyers without a limit specified in + [=auction config/per buyer group limits=]. + : per buyer priority signals + :: Null or an [=ordered map=] whose [=map/keys=] are [=origins=] and whose [=map/values=] are + [=ordered maps=], whose [=map/keys=] are [=strings=] and whose [=map/values=] are {{double}}. + Per-buyer sparse vector whose dot product with [=interest group/priority vector=] is used to + calculate interest group priorities. No signal's key starts with "browserSignals.", which is + reserved for values coming from the browser. + : all buyers priority signals + :: Null or an [=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are + {{double}}. + Merged with [=auction config/per buyer priority signals=] before calculating per-interest group + priorities. In the case both have entries with the same key, the entry in + `per_buyer_priority_signals` takes precedence. No signals key start with "browserSignals.", which + is reserved for values coming from the browser. + : component auctions + :: A [=list=] of [=auction config=]s. + Nested auctions whose results will also participate in a top level auction. Only the top level + [=auction config=] can have component auctions. + : seller experiment group id + :: Null or an {{unsigned short}}, initially null. + Optional identifier for an experiment group to support coordinated experiments with the seller's + trusted server. + : per buyer experiment group ids + :: An [=ordered map=] whose [=map/keys=] are [=origins=] and whose [=map/values=] are + {{unsigned short}}s. + [=map/Keys=] are buyers whose [=origin/schemes=] must be "`https`". [=map/Values=] are + identifiers for experiment groups, to support coordinated experiments with buyers' trusted servers. + : all buyer experiment group id + :: Null or an {{unsigned short}}, initially null. + Optional identifier for an experiment group to support coordinated experiments with buyers' + trusted servers for buyers without a specified experiment group. + : pending promise count + :: An integer, initially 0. The number of things that are pending that are needed to score + everything. It includes waiting for {{Promise}}s [=auction config/auction signals=], + [=auction config/per buyer signals=], [=auction config/per buyer currencies=], + [=auction config/per buyer timeouts=], [=auction config/direct from seller signals header ad slot=], + [=auction config/seller signals=], or {{AuctionAdConfig/additionalBids}} whose {{Promise}}s are + not yet resolved. + : config idl + :: {{AuctionAdConfig}}. + : resolve to config + :: A [=boolean=] or a {{Promise}}, initially false. + Whether the ad should be returned as a {{FencedFrameConfig}}, or otherwise as a [=urn uuid=]. + : seller currency + :: A [=currency tag=]. Specifies the currency bids returned by `scoreAd()` are expected to use, and + which reporting for this auction will agree on. + : per buyer currencies + :: A {{Promise}} or failure or an [=ordered map=] whose [=map/keys=] are [=origins=] and whose + [=map/values=] are [=currency tags=]. Specifies the currency bids returned by `generateBid()` or + `scoreAd()` in component auctions are expected to use. The initial value is an empty map. + : all buyers currency + :: A [=currency tag=]. Specifies the currency bids returned by `generateBid()` or `scoreAd()` in + component auctions are expected to use if [=auction config/per buyer currencies=] does not + specify a particular value. + : direct from seller signals header ad slot + :: Null, a [=string=], a {{Promise}}, or failure. Initially null. + : auction nonce + :: Null or a [=version 4 UUID=], initially null. + A unique identifier associated with this and only this invocation of + {{Window/navigator}}.{{Navigator/runAdAuction()}}. For multi-seller auctions, this ID is + uniquely associated with all {{AuctionAdConfig/componentAuctions}}. + This must come from a prior call to {{Window/navigator}}.{{Navigator/createAuctionNonce()}}. + This is only required for auctions that provide [=additional bids=], and each of those + [=additional bids=] must use the same auction nonce to ensure that each of them was intended for + this and only this auction. + : expects additional bids + :: A [=boolean=] or failure, initially false. + Specifies whether some bids will be provided as signed exchanges. Sets to failure if the + {{AuctionAdConfig/additionalBids}} {{Promise}} is [=rejected=].
-To wait until configuration input promises resolve given an [=auction config=] |auctionConfig|: -1. Wait until |auctionConfig|'s [=auction config/pending promise count=] is 0. -1. [=Assert=] |auctionConfig|'s [=auction config/auction signals=], [=auction config/seller signals=], - [=auction config/per buyer signals=], [=auction config/per buyer currencies=], - [=auction config/per buyer timeouts=], and [=auction config/direct from seller signals header ad slot=] - are not {{Promise}}s, and [=auction config/expects additional bids=] is false. -1. If |auctionConfig|'s [=auction config/auction signals=], [=auction config/seller signals=], - [=auction config/per buyer signals=], [=auction config/per buyer currencies=], - [=auction config/per buyer timeouts=], or [=auction config/direct from seller signals header ad slot=] - is failure, return failure. -1. Return. +To wait until configuration input promises resolve given an [=auction config=] +|auctionConfig|: + 1. Wait until |auctionConfig|'s [=auction config/pending promise count=] is 0. + 1. [=Assert=] |auctionConfig|'s [=auction config/auction signals=], [=auction config/seller signals=], + [=auction config/per buyer signals=], [=auction config/per buyer currencies=], + [=auction config/per buyer timeouts=], [=auction config/per buyer cumulative timeouts=], and + [=auction config/direct from seller signals header ad slot=] are not {{Promise}}s, and + [=auction config/expects additional bids=] is false. + 1. If |auctionConfig|'s [=auction config/auction signals=], [=auction config/seller signals=], + [=auction config/per buyer signals=], [=auction config/per buyer currencies=], + [=auction config/per buyer timeouts=], [=auction config/per buyer cumulative timeouts=], or + [=auction config/direct from seller signals header ad slot=] is failure, return failure. + 1. Return.
To recursively wait until configuration input promises resolve given an [=auction config=] |auctionConfig|: -1. [=list/For each=] |componentAuctionConfig| in |auctionConfig|'s [=auction config/component auctions=]: - 1. If the result of [=waiting until configuration input promises resolve=] given |componentAuctionConfig| is - failure, return failure. -1. Return the result of [=waiting until configuration input promises resolve=] given |auctionConfig|. + 1. [=list/For each=] |componentAuctionConfig| in |auctionConfig|'s + [=auction config/component auctions=]: + 1. If the result of [=waiting until configuration input promises resolve=] given + |componentAuctionConfig| is failure, return failure. + 1. Return the result of [=waiting until configuration input promises resolve=] given |auctionConfig|.
To handle an input promise in configuration given an [=auction config=] |auctionConfig|, a {{Promise}} |p|, and two sequences of steps, covering the parsing of the value and error-handling: -1. Increment |auctionConfig|'s [=auction config/pending promise count=]. -1. Let |resolvedAndTypeChecked| be the promise representing performing the following steps - [=upon fulfillment=] of |p| with |result|: - 1. Execute the steps to be run for parsing of the value given |result|. - 1. If no exception was [=exception/thrown=] in the previous step, then decrement |auctionConfig|'s - [=auction config/pending promise count=]. -1. [=Upon rejection=] of |resolvedAndTypeChecked|: - 1. Execute the steps for error-handling. - 1. Decrement |auctionConfig|'s [=auction config/pending promise count=]. + 1. Increment |auctionConfig|'s [=auction config/pending promise count=]. + 1. Let |resolvedAndTypeChecked| be the promise representing performing the following steps + [=upon fulfillment=] of |p| with |result|: + 1. Execute the steps to be run for parsing of the value given |result|. + 1. If no exception was [=exception/thrown=] in the previous step, then decrement + |auctionConfig|'s [=auction config/pending promise count=]. + 1. [=Upon rejection=] of |resolvedAndTypeChecked|: + 1. Execute the steps for error-handling. + 1. Decrement |auctionConfig|'s [=auction config/pending promise count=].
- To look up per-buyer currency given an [=auction config=] |auctionConfig|, and an [=origin=] |buyer|: +To look up per-buyer currency given an [=auction config=] |auctionConfig|, and an +[=origin=] |buyer|: 1. Let |perBuyerCurrency| be |auctionConfig|'s [=auction config/all buyers currency=] - 1. Assert: |auctionConfig|'s [=auction config/per buyer currencies=] is an [=ordered map=]. + 1. [=Assert=] that |auctionConfig|'s [=auction config/per buyer currencies=] is an [=ordered map=]. 1. If |auctionConfig|'s [=auction config/per buyer currencies=][|buyer|] [=map/exists=], then set |perBuyerCurrency| to |auctionConfig|'s [=auction config/per buyer currencies=][|buyer|]. 1. Return |perBuyerCurrency|
-

Per buyer bid generator

+

Bid generator

-A per buyer bid generator is an [=ordered map=] whose [=map/keys=] are [=URLs=] representing -[=interest group/trusted bidding signals urls=], and whose [=map/values=] are +A per buyer bid generator is an [=ordered map=] whose [=map/keys=] are [=URLs=] +representing [=interest group/trusted bidding signals urls=], and whose [=map/values=] are [=per signals url bid generators=]. -

Per signals url bid generator

- -A per signals url bid generator is an [=ordered map=] whose [=map/keys=] are [=origins=] +A per signals url bid generator is an [=ordered map=] whose [=map/keys=] are [=origins=] representing [=interest group/joining origins=], and whose [=map/values=] are [=lists=] of [=interest groups=]. +

Generated bid

-

Previous win

- -The [=interest group=]'s auction win history, to allow on-device frequency capping. - - -
-: time -:: A [=moment=]. Approximate time the [=interest group=] won an auction. -: ad json -:: A [=string=]. A JSON serialized object corresponding to the ad that won the auction. +A generated bid is a bid that needs to be scored by the seller. The bid is either the +result of [=evaluating a bidding script=], or an [=additional bid=] provided by the +[:Ad-Auction-Additional-Bid:] response headers. +
+ : bid + :: A [=bid with currency=]. If the [=bid with currency/value=] is zero or negative, then this + [=interest group=] will not participate in the auction. + : bid in seller currency + :: A {{double}} or null. An equivalent of the original bid in seller's currency. This is either + the original bid if the currency already matched, or a conversion provided by `scoreAd()`. + : ad + :: A [=string=]. JSON string to be passed to the scoring function. + + Issue: TODO: Check whether [=generated bid/ad descriptor=] can be moved to + [=generated bid/bid ad=] to avoid duplication + (WICG/turtledove#868). + : ad descriptor + :: An [=ad descriptor=]. Render URL and size of the bid's ad. + : ad component descriptors + :: Null or a [=list=] of [=ad descriptors=]. Ad components associated with bid, if any. May have at + most 20 [=list/items=]. Must be null if the interest group making this bid has a null + [=interest group/ad components=] field. + : ad cost + :: Null or a {{double}}. Advertiser click or conversion cost passed from `generateBid()` to + `reportWin()`. Negative values will be ignored and not passed. Will be + [=round a value|stochastically rounded=] when passed. + : modeling signals + :: Null or an {{unsigned short}}. A 0-4095 integer (12-bits) passed to `reportWin()`, with noising. + : interest group + :: An [=interest group=], whose `generateBid()` invocation generated this bid, or specified by the + additional bid. + : bid ad + :: The [=interest group ad=] within [=generated bid/interest group=] to display. + : modified bid + :: Null or a [=bid with currency=]. Being null for top level auction. + The bid value a component auction's `scoreAd()` script returns. + : bid duration + :: A [=duration=] in milliseconds. How long it took to run `generateBid()`. + : provided as additional bid + :: A [=boolean=], initially false.
-

Bid with currency

-Numeric value of a bid and the currency it is in. +A bid with currency is a numeric value of a bid and the currency it is in.
-: value -:: A {{double}}. The value of the bid. -: currency -:: A [=currency tag=]. The currency the bid is in. - + : value + :: A {{double}}. The value of the bid. + : currency + :: A [=currency tag=]. The currency the bid is in.
-

Generated bid

- -A bid that needs to be scored by the seller. The bid is either the output of running a Protected -Audience `generateBid()` script, or an additional bid provided by the [:Ad-Auction-Additional-Bid:] -response headers. +An ad descriptor is the render URL and size of an ad. -
-: bid -:: A [=bid with currency=]. If the [=bid with currency/value=] is zero or negative, then this - [=interest group=] will not participate in the auction. -: bid in seller currency -:: A {{double}} or null. An equivalent of the original bid in seller's currency. This is either the - original bid if the currency already matched, or a conversion provided by `scoreAd()`. -: ad -:: A [=string=]. JSON string to be passed to the scoring function. - - Issue: TODO: Check whether [=generated bid/ad descriptor=] can be moved to - [=generated bid/bid ad=] to avoid duplication - (WICG/turtledove#868). -: ad descriptor -:: An [=ad descriptor=]. Render URL and size of the bid's ad. -: ad component descriptors -:: Null or a [=list=] of [=ad descriptors=]. Ad components associated with bid, if any. May have at - most 20 [=list/items=]. Must be null if the interest group making this bid has a null - [=interest group/ad components=] field. -: ad cost -:: Null or a {{double}}. Advertiser click or conversion cost passed from `generateBid()` to - `reportWin()`. Negative values will be ignored and not passed. Will be - [=round a value|stochastically rounded=] when passed. -: modeling signals -:: Null or an {{unsigned short}}. A 0-4095 integer (12-bits) passed to `reportWin()`, with noising. -: interest group -:: An [=interest group=], whose `generateBid()` invocation generated this bid, or specified by the - additional bid. -: bid ad -:: The [=interest group ad=] within [=generated bid/interest group=] to display. -: modified bid -:: Null or a [=bid with currency=]. Being null for top level auction. - The bid value a component auction's `scoreAd()` script returns. -: bid duration -:: A [=duration=] in milliseconds. How long it took to run `generateBid()`. -: provided as additional bid -:: A [=boolean=], initially false. +
+ : url + :: A [=URL=], which will be rendered to display the [=ad creative=] if this bid wins the auction. + : size + :: Null or an [=ad size=], initially null. +
+An ad size is the width and height of an ad. +
+ : width + :: A {{double}}. + : width units + :: A [=string=]. Can only be one of "px" (pixel), "sh" (screen height), and "sw" (screen width). + : height + :: A {{double}}. + : height units + :: A [=string=]. Can only be one of "px" (pixel), "sh" (screen height), and "sw" (screen width).
-

Ad descriptor

+

Direct from seller signals

-The render URL and size of an ad. - -
-: url -:: A [=URL=], which will be rendered to display the [=ad creative=] if this bid wins the auction. -: size -:: Null or an [=ad size=], initially null. +A direct from seller signals key is a [=struct=] with the following [=struct/items=]: +
+ : seller + :: An [=origin=]. Matches the origin that served the captured [:Ad-Auction-Signals:] header. + : ad slot + :: A [=string=]. Matches the `adSlot` key of the JSON dictionaries in the top-level array of the + [:Ad-Auction-Signals:] value.
-

Ad size

- -Width and height of an ad. - -
-: width -:: A {{double}}. -: width units -:: A [=string=]. Can only be one of "px" (pixel), "sh" (screen height), and "sw" (screen width). -: height -:: A {{double}}. -: height units -:: A [=string=]. Can only be one of "px" (pixel), "sh" (screen height), and "sw" (screen width). +A direct from seller signals is a [=struct=] with the following [=struct/items=]: +
+ : auction signals + :: Null or a [=string=]. + Opaque JSON data passed to both buyers' and the seller's [=script runners=]. + : seller signals + :: Null or a [=string=]. + Opaque JSON data passed to the seller's [=script runner=]. + : per buyer signals + :: A [=map=] whose [=map/keys=] are [=origins=] and whose [=map/values=] are [=strings=]. + [=map/Keys=] are buyers whose [=origin/scheme=] must be "`https`". [=map/Values=] are opaque + JSON data passed to corresponding buyer's [=script runner=].
-

Score ad output

+

Score ad output

The output of running a Protected Audience `scoreAd()` script, is represented using the following type:
@@ -4479,8 +4726,8 @@ TODO: This also has an ad field, which should behave similar to the way {{ScoreA
 affects [=generated bid/modified bid=], and then affecting the adMetadata parameter to scoreAd.
 
 
- To process scoreAd output given an [=ECMAScript/Completion Record=] |result|: + 1. If |result| is an an [=ECMAScript/abrupt completion=], return failure. 1. If |result|.\[[Value]] is a [=Number=]: 1. Let |checkedScore| be the result of [=converted to an IDL value|converting=] @@ -4498,62 +4745,62 @@ To process scoreAd output given an [=ECMAScript/Completion Record=] | 1. Return |resultIDL|.
-

Leading bid info

+

Leading bid info

-Information of the auction's leading bid so far when ranking scored bids. +A leading bid info is the information of the auction's leading bid so far when ranking +scored bids.
-: top score -:: A {{double}}, initially 0.0. The highest score so far. -: top bids count -:: An integer, initially 0. The number of bids with the same `top score`. -: at most one top bid owner -:: A [=boolean=], initially true. Whether all bids of `top score` are from the same interest - group owner. -: leading bid -:: Null or a [=generated bid=]. The leading bid of the auction so far. -: auction config -:: An [=auction config=]. The auction config of the auction which generated this - [=leading bid info/leading bid=]. -: second highest score -:: A {{double}}, initially 0.0. The second highest score so far. If more than one bids tie with - `top score`, this will be set to `top score`. -: highest scoring other bids count -:: An integer, initially 0. The number of bids with the same `second highest score`. -: highest scoring other bid -:: Null or a [=generated bid=]. The second highest scoring other bid. -: highest scoring other bid owner -:: Null or an [=origin=], initially null. The interest group owner that made bids with the - `second highest score`. Set to null if there are more than one owners made bids with the - `second highest score`. -: top level seller -:: Null or a [=string=]. The seller in the top level auction. Only set for component auctions, null - otherwise. -: top level seller signals -:: Null or a [=string=]. Signals from the seller in the top level auction, produced as the output - of the top-level seller's `reportResult()` method. Only set for component auctions, - null otherwise. -: component seller -:: Null or a [=string=]. Seller in component auction which generated this - [=leading bid info/leading bid=]. Only set the top level auction when component auctions are - present, null otherwise. -: bidding data version -:: Null or an {{unsigned long}}. - Data-Version value from the trusted bidding signals server's response(s). Will only be not null if - the Data-Version header was provided and had a consistent value for all of the trusted bidding - signals server responses used to construct the trustedBiddingSignals. -: scoring data version -:: Null or an {{unsigned long}}. - Data-Version value from the trusted scoring signals server's response. Will only be not null if - the Data-Version header was provided in the response headers from the trusted scoring signals - server. -: buyer reporting result -:: Null or a [=reporting result=], initially null. -: seller reporting result -:: Null or a [=reporting result=], initially null. -: component seller reporting result -:: Null or a [=reporting result=], initially null. - + : top score + :: A {{double}}, initially 0.0. The highest score so far. + : top bids count + :: An integer, initially 0. The number of bids with the same `top score`. + : at most one top bid owner + :: A [=boolean=], initially true. Whether all bids of `top score` are from the same interest + group owner. + : leading bid + :: Null or a [=generated bid=]. The leading bid of the auction so far. + : auction config + :: An [=auction config=]. The auction config of the auction which generated this + [=leading bid info/leading bid=]. + : second highest score + :: A {{double}}, initially 0.0. The second highest score so far. If more than one bids tie with + `top score`, this will be set to `top score`. + : highest scoring other bids count + :: An integer, initially 0. The number of bids with the same `second highest score`. + : highest scoring other bid + :: Null or a [=generated bid=]. The second highest scoring other bid. + : highest scoring other bid owner + :: Null or an [=origin=], initially null. The interest group owner that made bids with the + `second highest score`. Set to null if there are more than one owners made bids with the + `second highest score`. + : top level seller + :: Null or a [=string=]. The seller in the top level auction. Only set for component auctions, + null otherwise. + : top level seller signals + :: Null or a [=string=]. Signals from the seller in the top level auction, produced as the output + of the top-level seller's `reportResult()` method. Only set for component auctions, + null otherwise. + : component seller + :: Null or a [=string=]. Seller in component auction which generated this + [=leading bid info/leading bid=]. Only set the top level auction when component auctions are + present, null otherwise. + : bidding data version + :: Null or an {{unsigned long}}. + Data-Version value from the trusted bidding signals server's response(s). Will only be not null + if the [:Data-Version:] header was provided and had a consistent value for all of the trusted + bidding signals server responses used to construct the trustedBiddingSignals. + : scoring data version + :: Null or an {{unsigned long}}. + Data-Version value from the trusted scoring signals server's response. Will only be not null if + the [:Data-Version:] header was provided in the response headers from the trusted scoring + signals server. + : buyer reporting result + :: Null or a [=reporting result=], initially null. + : seller reporting result + :: Null or a [=reporting result=], initially null. + : component seller reporting result + :: Null or a [=reporting result=], initially null.
A reporting result is a [=struct=] with the following [=struct/items=]: