diff --git a/FLEDGE.md b/FLEDGE.md index b7136f1fd..320362838 100644 --- a/FLEDGE.md +++ b/FLEDGE.md @@ -28,10 +28,11 @@ See [the Protected Audience API specification](https://wicg.github.io/turtledove - [2.5.2 Using Response Headers](#252-using-response-headers) - [3. Buyers Provide Ads and Bidding Functions (BYOS for now)](#3-buyers-provide-ads-and-bidding-functions-byos-for-now) - [3.1 Fetching Real-Time Data from a Trusted Server](#31-fetching-real-time-data-from-a-trusted-server) + - [3.1.1 Cross-Origin Trusted Server Signals](#311-cross-origin-trusted-server-signals) - [3.2 On-Device Bidding](#32-on-device-bidding) - [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.4.1 Flexible Component Ad Selection Considering k-Anonymity](#341-target-num-component-ads) + - [3.4.1 Flexible Component Ad Selection Considering k-Anonymity](#341-flexible-component-ad-selection-considering-k-anonymity) - [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) @@ -134,14 +135,11 @@ const myGroup = { 'trustedBiddingSignalsSlotSizeMode' : 'slot-size', 'maxTrustedBiddingSignalsURLLength' : 10000, 'userBiddingSignals': {...}, - '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', ...}], + 'ads': [{renderUrl: shoesAd1, sizeGroup: 'group1', ...}, + {renderUrl: shoesAd2, sizeGroup: 'group2', ...}], + 'adComponents': [{renderUrl: runningShoes1, sizeGroup: 'group2', ...}, + {renderUrl: runningShoes2, sizeGroup: 'group2', ...}, + {renderUrl: gymShoes, sizeGroup; 'group2', ...}], 'adSizes': {'size1': {width: '100', height: '100'}, 'size2': {width: '100', height: '200'}, 'size3': {width: '75', height: '25'}, @@ -251,9 +249,9 @@ The `adComponents` field contains the various ad components (or "products") that 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. +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 could in the future also be considered a size group, so you don't need to manually define singleton size groups. -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. +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://developers.google.com/privacy-sandbox/relevance/protected-audience-api/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: @@ -277,7 +275,7 @@ have the same origin, response header, fragment, or query requirements. (You can find detailed error conditions for all fields in step 6 of [the `joinAdInterestGroup()` section of the spec](https://wicg.github.io/turtledove/#dom-navigator-joinadinterestgroup)). -The browser will only render 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. +The browser will only render 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://developers.google.com/privacy-sandbox/relevance/protected-audience-api/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. Similar to [the key-value server](#31-fetching-real-time-data-from-a-trusted-server), the server keeping track of which ad URLs are k-anonymous is publicly queryable. The ad URLs are not supposed to target small groups of users (less than k users). For these reasons, and also in the interest of passing the k-anonymity check, the ad URLs should not contain PII, or sensitive information. @@ -292,7 +290,7 @@ If any of these per-owner limits are exceeded, the interest group(s) that would #### 1.3 Permission Delegation -When a frame navigated to one domain calls joinAdInterestGroup(), leaveAdInterestGroup(), or clearOriginJoinedAdInterestGroups() for an interest group with a different owner, the browser will fetch the URL https://owner.domain/.well-known/interest-group/permissions/?origin=frame.origin, where `owner.domain` is domain that owns the interest group and `frame.origin` is the origin of the frame. The fetch uses the `omit` [credentials mode](https://fetch.spec.whatwg.org/#concept-request-credentials-mode), using the [Network Partition Key](https://fetch.spec.whatwg.org/#network-partition-keys) of the frame that invoked the method. To avoid leaking cross-origin data through the returned Promise unexpectedly, the fetch uses the `cors` [mode](https://fetch.spec.whatwg.org/#concept-request-mode). The fetched response should have a JSON MIME type and be of the format: +When a frame navigated to one domain calls joinAdInterestGroup(), leaveAdInterestGroup(), or clearOriginJoinedAdInterestGroups() for an interest group with a different owner, the browser will fetch the URL https://owner.domain/.well-known/interest-group/permissions/?origin=frame.origin, where `owner.domain` is domain that owns the interest group and `frame.origin` is the origin of the frame. The fetch uses the `omit` [credentials mode](https://fetch.spec.whatwg.org/#concept-request-credentials-mode), using the [Network Partition Key](https://fetch.spec.whatwg.org/#network-partition-keys) of the frame that invoked the method. To avoid leaking cross-origin data through the returned Promise unexpectedly, the fetch uses the `cors` [mode](https://fetch.spec.whatwg.org/#concept-request-mode). The fetched response should have a JSON MIME type, have a `Access-Control-Allow-Origin` that allows it to load from the calling origin, and be of the format: ``` { "joinAdInterestGroup": true/false, @@ -346,9 +344,9 @@ const myAuctionConfig = { 'maxTrustedScoringSignalsURLLength': 10000, '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/...', + 'requestedSize': {'width': '100sw', 'height': '200px'}, + 'allSlotsRequestedSizes': [{'width': '100sw', 'height': '200px'}, {'width': '200px', 'height': '300px'}, ...], + 'directFromSellerSignalsHeaderAdSlot': 'adSlot/1', 'sellerSignals': {...}, 'sellerTimeout': 100, 'sellerExperimentGroupId': 12345, @@ -383,8 +381,8 @@ const myAuctionConfig = { 'perBuyerMultiBidLimits': {'https://example.com': 10, '*': 5}, 'sellerCurrency:' : 'CAD', 'reportingTimeout' : 200, - 'deprecatedRenderURLReplacements':{{'${SELLER}':'exampleSSP'}, - {'%%SELLER_ALT%%':'exampleSSP'}}, + 'deprecatedRenderURLReplacements':{'${SELLER}':'exampleSSP', + '%%SELLER_ALT%%':'exampleSSP'}, 'componentAuctions': [ {'seller': 'https://www.some-other-ssp.com', 'decisionLogicURL': ..., @@ -411,7 +409,7 @@ The optional `requestedSize` field recommends a frame size for the auction, whic `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). +The optional `directFromSellerSignalsHeaderAdSlot` field can also be used to pass signals to the auction, similar to `sellerSignals`, `perBuyerSignals`, and `auctionSignals`. The difference is that signals from `directFromSellerSignalsHeaderAdSlot` are trusted to come from the seller because the content loads from response headers from an https fetch request made to the seller's origin, ensuring the authenticity and integrity of the signals. For more details, see [2.5 directFromSellerSignals](#25-additional-trusted-signals-directfromsellersignals) and [2.5.2 directFromSellerSignalsHeaderAdSlot](#252-using-response-headers). 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. @@ -495,7 +493,13 @@ bid a positive score. #### 2.2 Auction Participants -Each interest group the browser has joined and whose owner is in the list of `interestGroupBuyers` will have an opportunity to bid in the auction. See the "Buyers Provide Ads and Bidding Functions" section, below, for how interest groups bid. +Each interest group the browser has joined with: +* `owner` in the list of `interestGroupBuyers`. +* Non null `biddingLogicURL`. The fetch of `biddingLogicURL` and `biddingWasmHelperURL` (if specified) must succeed to bid. +* At least one registered creative in the `ads` element. This implies that [negative interest groups](#621-negative-interest-groups) cannot bid as they cannot contain `ads`. +* Priority, as stated by `priority` or calculated via `priorityVector`, is greater than or equal to 0. + +will have an opportunity to bid in the auction. See the "Buyers Provide Ads and Bidding Functions" section, below, for how interest groups bid. #### 2.3 Scoring Bids @@ -505,7 +509,7 @@ Once the bids are known, the seller runs code inside an _auction worklet_. With ``` scoreAd(adMetadata, bid, auctionConfig, trustedScoringSignals, browserSignals, - directFromSellerSignals) { + directFromSellerSignals, crossOriginTrustedSignals) { ... return {desirability: desirabilityScoreForThisAd, incomingBidInSellerCurrency: @@ -520,13 +524,13 @@ The function gets called once for each candidate ad in the auction. The argumen * adMetadata: Arbitrary metadata provided by the buyer. * bid: A numerical bid value. * auctionConfig: The auction configuration object passed to `navigator.runAdAuction()`. -* trustedScoringSignals: A value retrieved from a real-time trusted server chosen by the seller and reflecting the seller's opinion of this particular creative, as further described in [3.1 Fetching Real-Time Data from a Trusted Server](#31-fetching-real-time-data-from-a-trusted-server) below. (In the case of [ads composed of multiple pieces](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#34-ads-composed-of-multiple-pieces) this should instead be some collection of values, structure TBD.) +* trustedScoringSignals: A value retrieved from a real-time trusted server chosen by the seller and reflecting the seller's opinion of this particular creative, as further described in [3.1 Fetching Real-Time Data from a Trusted Server](#31-fetching-real-time-data-from-a-trusted-server) below. This is used when the server is same-origin to the seller; crossOriginTrustedSignals is used otherwise. * browserSignals: An object constructed by the browser, containing information that the browser knows and which the seller's auction script might want to verify: ``` { 'topWindowHostname': 'www.example-publisher.com', 'interestGroupOwner': 'https://www.example-dsp.com', 'renderURL': 'https://cdn.com/render_url_of_bid', - 'renderSize': {width: 100, height: 200}, /* if specified in the bid */ + 'renderSize': {'width': '100sw', 'height': '200px'}, /* if specified in the bid */ 'adComponents': ['https://cdn.com/ad_component_of_bid', 'https://cdn.com/next_ad_component_of_bid', ...], @@ -538,6 +542,11 @@ The function gets called once for each candidate ad in the auction. The argumen * 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`. +* crossOriginTrustedSignals: like `trustedScoringSignals`, but used when the server is cross-origin + to the seller script. The value is an object that has as a key the trusted server's origin, e.g. + `"https://example.org"`, and as value an object in format `trustedScoringSignals` uses. See + [3.1.1 Cross-Origin Trusted Server Signals](#311-cross-origin-trusted-server-signals) for more + details. The output of `scoreAd()` is an object with the following fields: * desirability: Number indicating how desirable this ad is. Any value that is zero or negative indicates that the ad cannot win the auction. (This could be used, for example, to eliminate any interest-group-targeted ad that would not beat a contextually-targeted candidate.) The winner of the auction is the ad object which was given the highest score. @@ -646,7 +655,17 @@ 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, 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. +When invoking `navigator.runAdAuction()`, `directFromSellerSignalsHeaderAdSlot` is used to lookup the signals intended for that auction: + +``` +navigator.runAdAuction({ + ... + directFromSellerSignalsHeaderAdSlot: "adSlot/1", + ... +}); +``` + +`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. @@ -755,6 +774,75 @@ For detailed specification and explainers of the trusted key-value server, see a - [FLEDGE Key/Value Server APIs Explainer](https://github.com/WICG/turtledove/blob/master/FLEDGE_Key_Value_Server_API.md) - [FLEDGE Key/Value Server Trust Model Explainer](https://github.com/privacysandbox/fledge-docs/blob/main/key_value_service_trust_model.md) +##### 3.1.1 Cross-Origin Trusted Server Signals + +If the key-value server is on a different origin than the corresponding script, additional steps are +needed to ensure that sensitive information is not inappropriately leaked to, or is manipulated by, +a third party. + +In the case of bidder signals, the following changes occur: +1. The fetch is a cross-origin [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) fetch + with `Origin:` set to the buyer script's origin. +2. The value is passed to `crossOriginTrustedSignals` parameter, not the `trustedBiddingSignals` + parameter, and there is one more level of nesting denoting the server's origin, e.g: + +``` +{ + 'https://www.kv-server.example': { + 'keys': { + 'key1': arbitrary_json, + 'key2': arbitrary_json, + ...}, + 'perInterestGroupData': { + 'name1': { + 'priorityVector': { + 'signal1': number, + 'signal2': number, + ...} + }, + ... + } + } +} +``` +3. The data version is passed in `browserSignals.crossOriginDataVersion`, not + `browserSignals.dataVersion`. + +Seller signals have additional requirements, as the `trustedScoringSignalsURL` is provided by a +context that is not required to be same-origin with the seller: +1. The seller script must provide an `Ad-Auction-Allow-Trusted-Scoring-Signals-From` response header, + a [structured headers list of strings](https://www.rfc-editor.org/rfc/rfc8941) describing origins + from which fetching trusted signals is permitted. The trusted scoring signals fetch may not begin + until this header is received, in order to avoid leaking bid information to a third party. + This means using a cross-origin trusted server for seller information may carry a peformance + penalty. +2. The fetch is a cross-origin [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) fetch + with `Origin:` set to the seller script's origin. +3. The value is passed to `crossOriginTrustedSignals` parameter, not the `trustedScoringSignals` + parameter, and there is one more level of nesting denoting the server's origin, e.g: + +``` +{ + 'https://www.kv-server.example': { + 'renderURL': {'https://cdn.com/render_url_of_bidder': arbitrary_value_from_signals}, + 'adComponentRenderURLs': { + 'https://cdn.com/ad_component_of_a_bid': arbitrary_value_from_signals, + 'https://cdn.com/another_ad_component_of_a_bid': arbitrary_value_from_signals, + ...} + } +} +``` +4. The data version is passed in `browserSignals.crossOriginDataVersion`, not + `browserSignals.dataVersion`. + +Note that older versions of Chrome did not support cross-origin trusted signals. You can query +whether support is available as: + +``` +navigator.protectedAudience && navigator.protectedAudience.queryFeatureSupport( + "permitCrossOriginTrustedSignals") +``` + #### 3.2 On-Device Bidding Once the trusted bidding signals are fetched, each interest group's bidding function will run, inside a bidding worklet associated with the interest group owner's domain. The buyer's JavaScript is loaded from the interest group's `biddingLogicURL`, which must expose a `generateBid()` function: @@ -762,7 +850,8 @@ Once the trusted bidding signals are fetched, each interest group's bidding func ``` generateBid(interestGroup, auctionSignals, perBuyerSignals, - trustedBiddingSignals, browserSignals, directFromSellerSignals) { + trustedBiddingSignals, browserSignals, directFromSellerSignals, + crossOriginTrustedSignals) { ... return {'ad': adObject, 'adCost': optionalAdCost, @@ -785,15 +874,19 @@ The arguments to `generateBid()` are: * interestGroup: The interest group object, as saved during `joinAdInterestGroup()` and perhaps updated via the `updateURL`. * `priority` and `prioritySignalsOverrides` are not included. They can be modified by `generatedBid()` calls, so could theoretically be used to create a cross-site profile of a user accessible to `generateBid()` methods, otherwise. + * `lifetimeMs` is not included. It's ambiguous what should be passed: the lifetime when the group was joined, + or the remaining lifetime. Providing the remaining lifetime would also potentially give access to more + granular timing information than the API would otherwise allow, when state is shared across interest + groups. * auctionSignals: As provided by the seller in the call to `runAdAuction()`. This is the opportunity for the seller to provide information about the page context (ad size, publisher ID, etc), the type of auction (first-price vs second-price), and so on. * perBuyerSignals: The value for _this specific buyer_ as taken from the auction config passed to `runAdAuction()`. This can include contextual signals about the page that come from the buyer's server, if the seller is an SSP which performs a real-time bidding call to buyer servers and pipes the response back, or if the publisher page contacts the buyer's server directly. If so, the buyer may wish to check a cryptographic signature of those signals inside `generateBid()` as protection against tampering. -* trustedBiddingSignals: An object whose keys are the `trustedBiddingSignalsKeys` for the interest group, and whose values are those returned in the `trustedBiddingSignals` request. +* trustedBiddingSignals: An object whose keys are the `trustedBiddingSignalsKeys` for the interest group, and whose values are those returned in the `trustedBiddingSignals` request. This used when the trusted server is same-origin with the buyer's script. * browserSignals: An object constructed by the browser, containing information that the browser knows, and which the buyer's auction script might want to use or verify. The `dataVersion` field will only be present 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. `topLevelSeller` is only present if `generateBid()` is running as part of a component auction. Additional fields can include information about both the context (e.g. the true hostname of the current page, which the seller could otherwise lie about) and about the interest group itself (e.g. times when it previously won the auction, to allow on-device frequency capping). Note that unlike for `reportWin()` the `joinCount` and `recency` in `generateBid()`'s browser signals *isn't* subject to the [noising and bucketing scheme](#521-noised-and-bucketed-signals). Furthermore, `recency` in `generateBid()`'s browser signals is specified in milliseconds, rounded to the nearest 100 milliseconds. ``` { '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 */ + 'requestedSize': {'width': '100sw', 'height': '200px'}, /* if specified in auction config */ 'joinCount': 3, 'recency': 3600000, 'bidCount': 17, @@ -810,6 +903,12 @@ The arguments to `generateBid()` are: * directFromSellerSignals is an object that may contain the following fields: * perBuyerSignals: Like auctionConfig.perBuyerSignals, but passed via the [directFromSellerSignals](#25-additional-trusted-signals-directfromsellersignals) mechanism. These are the signals whose subresource URL ends in `?perBuyerSignals=[origin]`. * 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`. +* crossOriginTrustedSignals: Like `trustedBiddingSignals`, but used when the trusted-server is + cross-origin to the buyer's script. The value is an object that has as a key the trusted + server's origin, e.g. `"https://www.kv-server.example"`, and as value an object in format + `trustedBiddingSignals` uses. See + [3.1.1 Cross-Origin Trusted Server Signals](#311-cross-origin-trusted-server-signals) for more + details. In the case of component auctions, an interest group's `generateBid()` function will be invoked in all component auctions for which it qualifies, though the `bidCount` value passed to future auctions will only be incremented by one for participation in that auction as a whole. @@ -830,7 +929,7 @@ The output of `generateBid()` contains the following fields: * modelingSignals (optional): 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. * targetNumAdComponents and numMandatoryAdComponents (both optional): Permits the browser to select only some of the returned adComponents in order to help - make the ad k-anonymous. See [Flexible Component Ad Selection Considering k-Anonymity](#341-target-num-component-ads) + make the ad k-anonymous. See [Flexible Component Ad Selection Considering k-Anonymity](#341-flexible-component-ad-selection-considering-k-anonymity) for more details. In case returning multiple bids is supported by the implementation in use, @@ -880,7 +979,7 @@ const maxAdComponents = navigator.protectedAudience ? navigator.protectedAudience.queryFeatureSupport("adComponentsLimit") : 20; ``` -The output of `generateBid()` can use the on-device ad composition flow through an optional adComponents field, listing additional URLs made available to the fenced frame the container URL is loaded in. The component URLs may be retrieved by calling `navigator.adAuctionComponents(numComponents)`, where numComponents will be capped to the maximum permitted value. To prevent bidder worklets from using this as a side channel to leak additional data to the fenced frame, exactly numComponents obfuscated URLs will be returned by this method, regardless of how many adComponent URLs were actually in the bid, even if the bid contained no adComponents, and the Interest Group itself had no adComponents either. +The output of `generateBid()` can use the on-device ad composition flow through an optional adComponents field, listing additional URLs made available to the fenced frame the container URL is loaded in. The fenced frame configs for the winning ads may be retrieved be calling `window.fence.getNestedConfigs()`, which will always return an Array of 40 fenced frame configs. Alternatively, URNs for the component ad URLs may be retrieved by calling `navigator.adAuctionComponents(numComponents)`, where numComponents will be capped to the maximum permitted value. To prevent bidder worklets from using this as a side channel to leak additional data to the fenced frame, both APIs will pad their result with fenced frame configs or URNs that map to about:blank, so the requested number of values will be returned, regardless of how many adComponent URLs were actually provided by the bid. ##### 3.4.1 Flexible Component Ad Selection Considering k-anonymity @@ -1002,7 +1101,7 @@ Reports are only sent and most interest group state changes (e.g. updating `prev ### 5. Event-Level Reporting (for now) -Once the winning ad has rendered in its Fenced Frame, the seller and the winning buyer each have an opportunity to perform logging and reporting on the auction outcome. The browser will call one reporting function in the seller's auction worklet and one in the winning buyer's bidding worklet. +Once the winning ad has rendered in its Fenced Frame, the seller(s) and the winning buyer each have an opportunity to perform logging and reporting on the auction outcome. The browser will call one reporting function in the seller's auction worklet and one in the winning buyer's bidding worklet; in the case of a multi-seller auction both the top-level and winning component seller's reporting function will be invoked. _As a temporary mechanism,_ these reporting functions will be able to send event-level reports to their servers. These reports can include contextual information, and can include information about the winning interest group if it is over an anonymity threshold. This reporting will happen synchronously, while the page with the ad is still open in the browser. @@ -1011,7 +1110,7 @@ In the long term, we need a mechanism to ensure that the after-the-fact reportin #### 5.1 Seller Reporting on Render -A seller's JavaScript (i.e. the same script, loaded from `decisionLogicURL`, that provided the `scoreAd()` function) can also expose a `reportResult()` function. This is called with the bid that won the auction, if applicable. For component auction seller scripts, `reportResult()` is only invoked if the bid that won the component auction also went on to win the top-level auction. +A seller's JavaScript (i.e. the same script, loaded from `decisionLogicURL`, that provided the `scoreAd()` function) can also expose a `reportResult()` function. This is called with the bid that won the auction, if applicable. ``` @@ -1021,6 +1120,8 @@ reportResult(auctionConfig, browserSignals, directFromSellerSignals) { } ``` +In a multi-seller auction `reportResult` will be called for both the top-level-seller and winning component seller. For the component auction sellers, `reportResult()` is only invoked if the bid that won their component auction also went on to win the top-level auction. The `signalsForWinner` passed to `reportWin` will come from the output of the winning component seller's `reportResult`. + The arguments to this function are: @@ -1030,7 +1131,7 @@ The arguments to this function are: * `topLevelSeller`, `topLevelSellerSignals`, and `modifiedBid` are only present for component auctions, while `componentSeller` is only present for top-level auctions when the winner came from a component auction. * `modifiedBid` is the bid value a component auction's `scoreAd()` script passed to the top-level auction. * `topLevelSellerSignals` is the output of the top-level seller's `reportResult()` method. - * `highestScoringOtherBid` is the value of a bid with the second highest score in the auction. It may be greater than `bid` since it's a bid instead of a score, and a higher bid value may get a lower score. Rejected bids are excluded when calculating this signal. If there was only one bid, it will be 0. In the case of a tie, it will be randomly chosen from all bids with the second highest score, excluding the winning bid if the winning bid had the same score. A component seller's `reportWin()` function will be passed a bid with the second highest score in the component auction, not the top-level auction. It is not reported to top-level sellers in a multi-SSP case because we expect a top-level auction in this case to be first-price auction only: + * `highestScoringOtherBid` is the value of a bid with the second highest score in the auction. It may be greater than `bid` since it's a bid instead of a score, and a higher bid value may get a lower score. Rejected bids are excluded when calculating this signal. If there was only one bid, it will be 0. In the case of a tie, it will be randomly chosen from all bids with the second highest score, excluding the winning bid if the winning bid had the same score. A component seller's `reportResult()` function will be passed a bid with the second highest score in the component auction, not the top-level auction. It is not reported to top-level sellers in a multi-SSP case because we expect a top-level auction in this case to be first-price auction only: ``` { 'topWindowHostname': 'www.example-publisher.com', @@ -1057,7 +1158,7 @@ The `browserSignals` argument must be handled carefully to avoid tracking. It c 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. Callers of `sendReportTo()` should avoid assembling URLs longer than browser's URL length limits (e.g. [2MB for Chrome](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/url_display_guidelines/url_display_guidelines.md#url-length)) as these may not be reported. The URL 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. Please see [the Privacy Sandbox enrollment attestation model](https://github.com/privacysandbox/attestation#the-privacy-sandbox-enrollment-attestation-model). Eventually reporting will go through the Private Aggregation API once it has been developed. -The output of `reportResult()` is not used for reporting, but rather as an input to the buyer's reporting function. +The output of `reportResult()` is not used for reporting, but rather as an input to the buyer's reporting function. If there is no output or the output is not JSON-serializable (i.e. supported by JSON.stringify()), it will be `null` in `reportWin()`'s `sellerSignals`. #### 5.2 Buyer Reporting on Render and Ad Events @@ -1078,7 +1179,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, 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 `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 at least [Q1 2025](https://developers.google.com/privacy-sandbox/relevance/protected-audience-api/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. diff --git a/FLEDGE_Key_Value_Server_API.md b/FLEDGE_Key_Value_Server_API.md index d1b61aaa4..a4567ba13 100644 --- a/FLEDGE_Key_Value_Server_API.md +++ b/FLEDGE_Key_Value_Server_API.md @@ -1,6 +1,4 @@ -> FLEDGE has been renamed to Protected Audience API. To learn more about the name change, see the [blog post](https://privacysandbox.com/intl/en_us/news/protected-audience-api-our-new-name-for-fledge) - -# FLEDGE Key/Value Server APIs Explainer +# Protected Audience Key/Value Server APIs Explainer Authors: @@ -9,9 +7,9 @@ Authors: ## Summary -[FLEDGE](https://github.com/WICG/turtledove/blob/main/FLEDGE.md) is a privacy-preserving API that facilitates interest group based advertising. Trusted -servers in FLEDGE are used to add real-time signals into ad selection for both -buyers and sellers. The FLEDGE proposal specifies that these trusted servers +[Protected Audience](https://github.com/WICG/turtledove/blob/main/FLEDGE.md) is a privacy-preserving API that facilitates interest group based advertising. Trusted +servers in Protected Audience are used to add real-time signals into ad selection for both +buyers and sellers. The Protected Audience proposal specifies that these trusted servers should provide basic key-value lookups to facilitate fetching these signals but do no event-level logging or have other side effects. @@ -39,7 +37,7 @@ though the keys may be unique across namespaces in today’s use cases. * For an SSP, there are `renderUrls` and `adComponentRenderUrls`. The -[FLEDGE explainer](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#31-fetching-real-time-data-from-a-trusted-server) +[Protected Audience explainer](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#31-fetching-real-time-data-from-a-trusted-server) provides more context about these namespaces. ## Query API version 2 @@ -48,25 +46,58 @@ __Query versions 2 and beyond are specifically designed for the trusted TEE key/ ### Background -In this version we present a protocol that enables trusted communication between Chrome and the trusted key/value service. The protocol assumes a functioning trust model as described in [the key/value service explainer](https://github.com/privacysandbox/fledge-docs/blob/main/key_value_service_trust_model.md) is in place, primarily that only service implementations recognized by Privacy Sandbox can obtain private decryption keys, and the mechanism for the client and service to obtain cryptographic keys is available but outside the scope of this document. +In this version we present a protocol that enables trusted communication between Chrome and the trusted key/value service. The protocol assumes a functioning trust model as described in [the key/value service explainer](https://github.com/privacysandbox/protected-auction-services-docs/blob/main/key_value_service_trust_model.md) is in place, primarily that only service implementations recognized by Privacy Sandbox can obtain private decryption keys, and the mechanism for the client and service to obtain cryptographic keys is available but outside the scope of this document. -On a high level, the protocol is based on HTTPS + [Oblivious HTTP](https://datatracker.ietf.org/doc/draft-ietf-ohai-ohttp/)(OHTTP). +On a high level, the protocol is similar to the [Bidding & Auction services protocol](https://github.com/WICG/turtledove/blob/main/FLEDGE_browser_bidding_and_auction_API.md), with (bidirectional) [HPKE encryption](https://datatracker.ietf.org/doc/rfc9180/). * TLS is used to ensure that the client is talking to the real service operator (identified by the domain). FLEDGE enforces that the origin of the trusted server matches the config owner ([interest group owner](https://wicg.github.io/turtledove/#joining-interest-groups) for the trusted bidding signal server or [auction config’s seller](https://wicg.github.io/turtledove/#running-ad-auctions) for the trusted scoring signal server). -* OHTTP is used to ensure that the message is only visible to the approved versions of services inside the trusted execution environment (TEE). - * The reason to use OHTTP is that the request must be encrypted and can only be decrypted by the trusted service itself. A notable alternative protocol is TLS which in addition to the domain validation, validates the service identity attestation. However, attestation verification as part of TLS can present performance challenges and is still being evaluated. +* HPKE is used to ensure that the message is only visible to the approved versions of services inside the trusted execution environment (TEE). + * The reason to use HPKE is that the request must be encrypted and can only be decrypted by the trusted service itself. A notable alternative protocol is TLS which in addition to the domain validation, validates the service identity attestation. However, attestation verification as part of TLS can present performance challenges and is still being evaluated. + * The protocol to configure the HPKE is roughly based on the [Oblivious HTTP proposal](https://datatracker.ietf.org/doc/rfc9458/). -For more information on the design, please refer to [the trust model explainer](https://github.com/privacysandbox/fledge-docs/blob/main/key_value_service_trust_model.md). +For more information on the design, please refer to [the trust model explainer](https://github.com/privacysandbox/protected-auction-services-docs/blob/main/key_value_service_trust_model.md). ### Overview ![V2 API diagram](assets/fledge_kv_server_v2_api.png) -HTTP(s) is used to transport data. The message body is an encrypted binary HTTP message as specified by the Oblivious HTTP standard. The binary HTTP message, as it is encrypted, can contain sensitive information. The headers can be used to specify metadata such as compression algorithms. The body is an optionally compressed data structure of the actual request/response message. Padding is applied to the serialized binary HTTP message. +HTTPS is used to transport data. The method is `POST`. + +The HTTP POST body is encrypted. + +#### Encryption + +We will use [Oblivious HTTP](https://datatracker.ietf.org/doc/draft-ietf-ohai-ohttp/) with the following configuration for encryption: + +* 0x0020 DHKEM(X25519, HKDF-SHA256) for KEM (Key encapsulation mechanisms) +* 0x0001 HKDF-SHA256 for KDF (key derivation functions) +* AES256GCM for AEAD scheme. + +Since we are [repurposing the OHTTP encapsulation mechanism, we are required to define new media types](https://www.rfc-editor.org/rfc/rfc9458.html#name-repurposing-the-encapsulati): +* The OHTTP request media type is “message/ad-auction-trusted-signals-request” +* The OHTTP response media type is “message/ad-auction-trusted-signals-response” + +Note that these media types are [concatenated with other fields when creating the HPKE encryption context](https://www.rfc-editor.org/rfc/rfc9458.html#name-encapsulation-of-requests), and are not HTTP content or media types. + +Inside the ciphertext, the request/response is framed with a 5 byte header, where the first byte is the format+compression byte, and the following 4 bytes are the length of the request message in network byte order. Then the request is zero padded to a set of pre-configured lengths. + +The lower 2 bits are used for compression specification. The higher 6 bits are currently unused. + +#### Format+compression byte + +- `0x00` - [CBOR](https://www.rfc-editor.org/rfc/rfc8949.html) no compression +- `0x01` - CBOR compressed in brotli +- `0x02` - CBOR compressed in gzip + +For request, the byte value is 0x00. For response, the byte value depends on the “acceptCompression” field in the request and the server behavior. + +#### Padding + +Padding is applied with sizes as multiples of 2^n KBs ranging from 0 to 2MB. So the valid response sizes will be [0, 128B, 256B, 512B, 1KB, 2KB, 4KB, 8KB, 16KB, 32KB, 64KB, 128KB, 256KB, 512KB, 1MB, 2MB]. ### Core data -Core request and response data structures are all in JSON. +Core request and response data structures are all in [CBOR](https://www.rfc-editor.org/rfc/rfc8949.html). The schema below is defined following the spec by https://json-schema.org/. @@ -76,127 +107,78 @@ The API is generic, agnostic to DSP or SSP use cases. ##### Request version 2.0 -Requests are not compressed. Compression could save size but may add latency. Request size is presumed to be small, so compression may not improve overall performance. Version 2 will not use compression for the request but more experimentation is necessary to determine if compression is an overall win and should be included in future protocol versions. +Requests are not compressed. Compression could save size but may add latency. Request size is presumed to be small, so compression may not improve overall performance. Version 2 will initially not implement compression for the request but more experimentation is necessary to determine if compression is an overall win and should be implemented in the future. -In the request, one major difference from [V1](#query-api-version-1) is that the keys are now grouped. There is a tree-like hierarchy: +In the request, one major difference from V1/BYOS is that the keys are now grouped. There is a tree-like hierarchy: -* Each request contains one or more partitions. Each partition is a collection of keys that can be processed together by the service without any potential privacy leakage (For example, if the server uses User Defined Functions to process, one UDF call can only process one partition). This is controlled by the client. For example, Chrome may put all keys from the same interest group into one partition. With certain optimizations allowed, such as with [“executionMode: group-by-origin”](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#12-interest-group-attributes), keys from all interest groups with the same joining site may be in one partition. +* Each request contains one or more partitions. Each partition is a collection of keys that can be processed together by the service without any potential privacy leakage (For example, if the server uses [User Defined Functions](https://github.com/privacysandbox/protected-auction-services-docs/blob/main/key_value_service_user_defined_functions.md) to process, one UDF call can only process one partition). Keys from one interest group must be in the same partition. Keys from different interest groups with the same joining site may or may not be in the same partition, so the server User Defined Functions should not make any assumptions based on that. * Each partition contains one or more key groups. Each key group has its unique attributes among all key groups in the partition. The attributes are represented by a list of “Tags”. Besides tags, the key group contains a list of keys to look up. * Each partition has a unique id. * Each partition has a compression group field. Results of partitions belonging to the same compression group can be compressed together in the response. Different compression groups must be compressed separately. See more details below. The expected use case by the client is that interest groups from the same joining origin and owner can be in the same compression group. ![request structure](assets/fledge_kv_server_v2_req_structure.jpeg) -##### Available Tags - -###### Version 2023.01 - -
Tag category - | -Category description - | -Tag - | -Description - | -Restrictions - | -
Client awareness - | -Each key group has exactly one tag from this category. - | -structured - | -Browser defines the format of this key/value pair and will use the results for internal purposes. An example use case is the PerInterestGroupData in the FLEDGE explainer. - | -- | -
custom - | -Browser is oblivious to the keys and directly passes the results to DSP/SSP javascript functions - | -- | -||
Namespace - | -Each key group has exactly one tag from this category. - | -interestGroupNames - | -Names of interest groups in the encompassing partition. - | -The service expects the client to only pair this with ‘structured’ tag - | -
keys - | -“keys” is a list of trustedBiddingSignalsKeys strings. - | -The service expects the client to only pair this with ‘custom’ tag - | -||
renderUrls - | -Similarly, sellers may want to fetch information about a specific creative, e.g. the results of some out-of-band ad scanning system. This works in much the same way, with the base URL coming from the trustedScoringSignalsUrl property of the seller's auction configuration object. - | -|||
adComponentRenderUrls - | -
Tag category + | +Category description + | +Tag + | +Description + | +
Namespace + | +Each key group has exactly one tag from this category. + | +interestGroupNames + | +Names of interest groups in the encompassing partition. + | +
keys + | +“keys” is a list of trustedBiddingSignalsKeys strings. + | +||
renderUrls + | +Similarly, sellers may want to fetch information about a specific creative, e.g. the results of some out-of-band ad scanning system. This works in much the same way, with the base URL coming from the trustedScoringSignalsUrl property of the seller's auction configuration object. + | +||
adComponentRenderUrls + | +
Header - | -Description - | -
Accept-Encoding - | -What compression algorithm the client can accept
- -Optional. - |
-
X-kv-query-request-version - | -Request version used as specified in this document. - | -
Header - | -Description - | -
Content-Encoding - | -What compression algorithm the service used.
- -Set if the client specifies “Accept-Encoding” header. - |
-
x-kv-query-response-version - | -Response version used as specified in this document. - | -
[fetchLater API](https://developer.chrome.com/blog/fetch-later-api-origin-trial)
+ (entered as [comment](https://github.com/WICG/turtledove/issues/896#issuecomment-2233667864) on #896)
+
+ Chrome is [migrating](https://issues.chromium.org/issues/40236167) keep-alive request handling from the “renderer” (front-end) to the “browser” process (back-end). [Explainer](https://docs.google.com/document/d/1ZzxMMBvpqn8VZBZKnb7Go8TWjnrGcXuLS_USwVVRUvY/edit#). Attribution Reporting API (ARA) supports event and trigger header registrations on background requests, and this will move to the browser. ARA team has [extended that hook](https://issues.chromium.org/issues/40242339#comment48) so that fetchLater
API requests will also be able to set ARA headers. Requesting that Protected Audience header processing also support fetchLater
.
+
+
+
+# Announcements
+
+The Microsoft Edge folks are holding every-second-Thursday meetings at this same hour to discuss their very similar "Ad Selection API" work. See https://github.com/WICG/privacy-preserving-ads/issues/50 for logistics.
+
+Everyone is responsible for checking the accuracy of the notes doc, especially the part with their own comments - so go back later and make sure the notes are accurate. You can even fix them on Github later!! :)
+
+
+# Notes
+
+[Github: Third Party Reporting #1220](https://github.com/WICG/turtledove/issues/1220)
+
+Matt Davies (BidSwitch):
+
+Applies strongly to BidSwitch but sure many others in the supply chain would require access to this. Idea is to additional reporting party URL w/in PA response that comes back from oRTB where credentials are provided to the SSP / seller, to then be able to create their own PA auction. Idea would be to add a few extensions for 3P IG signals (HTP, URL, DATA URL) which would allow 3Ps to have access to auction results. You may have a direct participant or non-direct chain, it might be that the IG allows DPS A to trade with SSP B directly. The same IG may also be used on another transaction that goes through BidSwitch or 3P operator where they need to register that they are in the chain. It would be useful to understand if it’s a worthwhile use case.
+
+Michael Kleber (Chrome): Let me ask some questions to understand the relationship of parties. Trying to scope the story; in the PA world, we tend to think about there being 2 parties, buyer and seller, and we have possibility of component vs top-level seller, but don’t need to worry about that now. The way that we think about things like how do ppl get onto the pub page? The pub page is owned by the pub (1st party) and then the fact that anyone else is on the pub page is the result of some delegation / invitation, so we think about it as the pub invites some list of sellers to sell inventory - SSPs - who then invite buyers to buy into their auctions. If pub delegates to seller, and seller to buyer… where does BidSwitch use case fit in chain of delegation? Who is it that invited BidSwitch to be a participant in this auction?
+
+Matt: Depends how much you want to separate oRTB from PA. Basis is that oRTB side of equation starts that process; oRTB request goes to DSP, they send a response with PA credentials, from there seller configs; after that it’s sent to the seller and the auction is between just the buyer and seller. Part of where BSW comes in is in oRTB; many DSPs will have 15 top supply partners w direct integration, but work with 40-50 smaller entities like BidSwitch or others e.g. Magnite. When you see application traffic you see SSP partners inviting other SSPs. There are chains of oRTB transactions where it goes through, but impressions and spend need to be recorded; agreement w DSP / SSP. Required to have certain live accurate data around giga transactions. For sake of argument, imagine SSP ABC, and XYZ DSP. They run through BidSwitch; so the SSP sends oRTB request, transmitted to dSP, the DSP would respond via BidSwitch about auction participation. We will then pass to SSP, who will create component auction, which will then invited DSP directly in component in auction. Once impression created, noone in the chain would be able to record and have access to transactions.
+
+Alonso Velasquez (Google PSB): Ultimately we need to understand whether this reporting is in the service of the buy or sell side, or representing both in some cases as it may be with BidSwitch. I gather that the way to scope this problem is, that there are adtech parties that are not the seller or buyer and are still in the service of the Advertiser or Publisher, that certain reporting capabilities are missing from the PA auction in order for these parties to get the reporting needed to render their services to the Publisher or Advertiser.
+
+Matt: While BSW can’t run component auction….
+
+Michael: It seems one natural answer is, why doesn’t BidSwitch run a component auction? If BidSwitch is a player that gets some amt of awareness of demand, and propagates demand to some collection of buyers that buy proxied through BidSwitch, then why isn’t it that BidSwitch is a component buyer in PA auction directly? What would prevent BidSwitch from being component seller?
+
+Matt: But the SSP we’re repping would also want to work their own component auction. There’s only top-line level seller (GAM / Prebid), then pub , seller, maybe exchange, buyer, etc. Only 2 components can run a component auction and no one else (?)
+
+Michael: Agreed, but why can’t we flatten that out and have BidSwitch be a component buyer directly in top level? Ah, but you’re saying BSW is only here because SSP invite, so SSP would get cut out of reporting chain?
+
+Matt: Yes, that would be the problem. There might be data providers that may need to also get access to this data. IGs belong to the DSPs not us, and we’re not privy what DSP wants to run in campaign at any given time.
+
+Michael: It seems like everything you are trying to do involves some kind of winning bid that went through BidSwitch during OpenRTB phase; that annotation is something winning component SSP is in good position to add, if they choose to. They know everything about their supply lines, when winning SSPs evaluates each bid they know they got it because of BidSwitch’s involvement in oRTB side. It’s possible for the reporting to happen, but only if the component SSP does the right thing. What if SSP doesn’t annotate the bid correctly?
+
+Matt: We wouldn’t want to rely on 3P for access…
+
+Michael: But isn’t it the SSP that invited BidSwitch?
+
+Matt: Yes but the tech or them to be able to feed the info back to us, would they add us to reportWin? It would be very useful, but it would also be useful to be able to state that we were def part of the auction instead of relying on the original SSP to confirm… what if it doesn’t happen 100% of the time?
+
+Harshad Mane (PubMatic): Another use case. Currently pubs rely on component sellers; let’s say pub is working w 5 SSPs, pub needs to combine all these reports offline. Pub doesn’t have way to get real time reports from auction.
+
+Michael: I agree we have been talking about getting direct reporting to the pub. Seems different from Matt’s use case; we all know why pub is entitled to reporting. They’re top of the chain of delegation of right to be on page, but now we’re talking about more steps removed from that. How do we even know who the parties are who are supposed to get reporting seems like the difficult part. Difficulty is – if BidSwitch is only in the auction because an SSP invited BidSwitch, then that SSP is the channel that sits between you and the browser. If SSP is not trustworthy to include BidSwitch, then anything I can do as a browser would only be in the reporting configuration proxied by the SSP.
+
+Brian May (Distillery): There is a question of trust, but also of people making mistakes and not recognizing it. If the SSP is missing something that needs to be visible.
+
+Alonso: Might be worthwhile to say we have infrastructure for both sellers and buyers to declare 3P destinations and which data from the auction they can receive (fenced frames, ad reporting infrastructure). Config needs to be done by seller / buyer still, but it’s feasible. [Additional edit from Alonso: we know that the ability to declare additional destinations is present for the buyer and not the seller, so this is perhaps an area to get feedback on below]. For more than a year we have known of 3Ps that need to get reporting; about a year ago enhanced flexibility for different kinds of reporting. Will share the explainer here: https://github.com/WICG/turtledove/blob/main/Fenced_Frames_Ads_Reporting.md . I encourage everyone to consider how far the reporting capabilities listed here from what you’re envisioning as the need and give us feedback on that.
+
+Warren (Media.net): Makes sense on buy side, but gap is on sell side; we don’t have a similar / easy way to have list of 3P behind use to get similar reporting. There’s no easy way built into the system for sellers to do what buyers can do.
+
+Michael: So the problem is that sellers would want an endpoint for reports to get sent to, but not be sent there all the time. There should be a step which makes it different from pubs getting duplicate reports, which could plausibly happen all the time. Somehow someone needs to trigger a win to have an additional party to get dupe copy of reporting.
+
+Warren: How do we trigger it, even when we figure out whose impression it is?
+
+Michael: As a browser person, I have to ask: why doesn’t the SSP server that receives the win report serve a response that is an HTTP redirect to deliver that report to another host in addition? Why can’t the SSP doing a browser redirect solve the problem?
+
+Matt: In our experience HTTP redirect leads to big discrepancies, 2-3%, which disappears when you have server to server APIs, then it’s 0.1%. It also leads to extra processes. We trust our SSPs implicitly, but as with anything, it is easier to do inhouse. Imagine chain, DSP send bid response to us, imagine there is something that shows which request it should be applied to.
+
+Roni (Index): Problem is if BSW represents buyer origin, there is nothing in reporting results to show it came from BSW. There is no notion of a “representative”. reportResult() only has IG owner and renderURL – it would be indistinguishable from a bid from the ultimate buyer that BSW is representing. So it’s not just a matter of calling sendReport() multiple times – there’s no way to know at reportResult() time that BSW was involved.
+
+Michael: If I understand correctly, the contextual response that was put there by BSW as part of OpenRTB contextual response, that should be enough info so that the SSP partner can look at contextual response as part of seller signal, and it should know all the bids coming from buyer, are bids that are in auction because of BidSwitch as an intermediary. Is that right? That the seller and buyer and seller signals are enough for SSPs to figure out whether to trigger BidSwitch reporting.
+
+Matt: Theoretically, unless the SSP knows which IG group won. They won’t know which path it went to.
+
+Michael: But they know who the buyer is, sounds like there is enough info.
+
+Matt: We don’t add anything to to PA response, we just send it back to them. There is nothing in that response that we would add to; if we were able to give a field to fill in in advance, they can put into that auction config to get automatically called when impression occurs.
+
+Michael: Part of your GitHub proposal is about wanting some new JS worklet coming from a new party that gets to execute on device. For that part of your request, as browser we’d prefer not to have additional worklets to contribute additional JS; possible but worst case scenario, in making the API more heavy. Having additional destinations is more appealing if the report can be built by the browser based on some static declaration, from an API design POV. Sending a browser-crafted report is a lot nicer than downloading someone’s JS to run a worklet. It would be great to know if there is a version of this proposal that could be implemented as a static config. I’m unclear on how triggering works, how the SSP can know which bids should get BSW reporting and which do not. Let me re-read notes and responses; if it’s really just a function of buyer / seller component auction and something that can be written in OpenRTB response.
+
+Roni: This is not merely about notifying BidSwitch, the seller needs to know that BidSwitch is involved at all stages. SSPs need to collect money from BSW in this scenario, so everything in the APIs – KV, scoreAd, reportResult, etc. – would need this awareness.
+
+Matt: At end of day we will still need to pay the SSP
+
+Michael: No one mentioned money changing hands goes through BidSwitch, not only an info flow.
+
+Matt: We are the proxy, we would invoice and pay SSP out is how we operate. We would be in billing chain as well. The only other option would be for SSP to be calling with new URL…
+
+Michael: The fact that a particular bid / buyer was proxied through BidSwitch, that info is knowable inside the auction, ScoreAd function, report win, report results, hopefully that’s still the case. Roni said that info needs to be known in the seller's key value server, which right now learns just the render URL, that adds further complication. Also Roni’s question is another complication – what if the same buyer has a direct relationship with the SSP AND a connection through BidSwitch?
+
+Roni: Nothing prevents me from onboarding a direct relationship, that can happen any day. I can map Brian, tomorrow it may be indistinguishable if Brian goes through Matt. The same IG fires, the same IG bid gets submitted, so I would continue to charge Brian. By design once that bid wins, that relationship is lost.
+
+Michael: It sounds like you’re increasingly making the case that the right answer is for BSW to be a component SSP.
+
+Matt: If we could do that, that would be amazing and we would build it out; we’ve been looking at it from the beginning, but it wasn’t in the spec. If we could run a component auction, passing details back and forth, that would create a third level auction.
+
+Michael: Point is that maybe we can flatten the tree and BidSwitch can be a component auction. Anyone bringing demand is a component auction.
+
+Warren: Comes back to previous issue about SSP then not having reporting… one or the other
+
+Michael: BidSwitch could send it reporting to the SSP that invited it to the auction.
+
+Brian: I think BidSwitch is more of an alias than a replacement.
+
+Paul (Chrome): What if 2 component auctions
+
+MIchael: I was thinking more linked component SSP that runs component auction could list each buy as a buyer or buyer-becaeuse-of-BidSwitch, making that level of aliasing more obvious. Then winning bid would come from buyer X or buyer X-because-of-BidSwitch. IG would belong to buyer X. If same buyer has both relationships, does that buyer bid twice in same component auction?
+
+Roni: Nothing prevents that from happening, multiple bids into the same auction. The KV doesn’t get the IG owner, I have to guess who owns the render URL. Now it would be another level of indirection. It’s not just who owns the IG, it’s who’s representing the buyer origin commercially?
+
+Brain: Suggest for Matt to mull over and come back
+
+Michael: If we propagate the buyer as part of reporting, and maybe each alias /annotation of buyer gets automatic reporting, maybe it’s enough to address this.
+
+Paul: What reporting are you looking for?
+
+Matt: Impression, spend, and click – did a billable event occur? From there we can run at least a record of the transaction occurring.
+
+Brian: If we’re reporting 3P data there needs to be consideration that the data wasn’t tampered with.
diff --git a/meetings/2024-07-24-FLEDGE-call-minutes.md b/meetings/2024-07-24-FLEDGE-call-minutes.md
new file mode 100644
index 000000000..67110afa4
--- /dev/null
+++ b/meetings/2024-07-24-FLEDGE-call-minutes.md
@@ -0,0 +1,237 @@
+# 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 = 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 July 24, 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/
+
+If the meeting disappears from your calendar: try leaving and re-joining that group
+
+
+## Attendees: please sign yourself in!
+
+
+
+1. Michael Kleber (Google Privacy Sandbox)
+2. Brian May (Dstillery)
+3. Eyal Segal (Taboola)
+4. Roni Gordon (Index Exchange)
+5. David Dabbs (Epsilon)
+6. Isaac Foster (MSFT Ads | The Avengers)
+7. Konstantin Stepanov (MSFT Ads)
+8. Felipe Gutierrez (MSFT Ads)
+9. Patrick McCann (Raptive)
+10. Sven May (Google Privacy Sandbox)
+11. Harshad Mane (PubMatic)
+12. Marco Lugo (NextRoll)
+13. Matt Kendall (Index Exchange)
+14. Elmostapha BEL JEBBAR (Lucead)
+15. Matt Davies (Bidswitch | Criteo)
+16. Tamara Yaeger (BidSwitch)
+17. Ivan Staritskii (Bidswitch | Criteo)
+18. B. McLeod Sims (Media.net)
+19. Owen Ridolfi (Flashtalking)
+20. Anthony Yam (Flashtalking)
+21. Harry Stevens (Bidswitch | Criteo)
+22. Becky Hatley (Flashtalking)
+23. Jason Lydon (Flashtalking …)
+24. Arthur Coleman (IDPrivacy)
+25. Alex Cone (Google Privacy Sandbox)
+26. Jeroune Rhodes (Privacy Sandbox)
+27. Alonso Velasquez (Google Privacy Sandbox)
+28. Kevin Lee (Google Privacy Sandbox)
+29. Ashley Irving (Google Privacy Sandbox)
+30. Sid Sahoo (Google Chrome)
+31. Orr Bernstein (Google Privacy Sandbox)
+32. Sathish Manickam (Google Privacy Sandbox)
+33. Fabian Höring (Criteo)
+34. Laura Morinigo (Samsung)
+35. Paul Jensen (Google Privacy Sandbox)
+36. Matt Menke (Google Chrome)
+37. Andrew Pascoe (NextRoll)
+38. Antoine Niek (Optable)
+39. Alex Peckham (Flashtalking)
+40. Guillaume Polaert (Pubstack)
+41. Sarah Harris (Flashtalking)
+42. Taranjit Singh (Jivox)
+43. Tim Taylor (Flashtalking)
+44. Viacheslav Levshukov (Microsoft)
+45. Josh Singh (Microsoft)
+46. Miguel Morales (IAB TechLab)
+47. Achim Schlosser (Bertelsmann)
+48. Paul Spadaccini (Flashtalking)
+49. Brian Schneider (Google Privacy Sandbox)
+50. Matthew Atkinson (Samsung)
+51. Jeremy Bao (Google Privacy Sandbox)
+52. Aymeric Le Corre (Lucead)
+53. Pierre Perez (Azerion)
+54. Shafir Uddin (Raptive)
+55. Premkumar Srinivasan (Microsoft Ads)
+56. Daniel Rojas (Google Privacy Sandbox)
+57. Courtney Johnson (Privacy Sandbox)
+58. Manny Isu (Google Privacy Sandbox)
+59. Amitava Ray Chaudhuri (Adobe Adcloud)
+60. Koji Ota(CyberAgent)
+61. Laurentiu Badea (OpenX)
+62. Suresh Chahal (Microsoft)
+63. Nick Colletti (Raptive)
+64. Abishai Gray (Google Privacy Sandbox)
+65. Veronica Kim (Raptive)
+66. Jinhwa Rustand (Nativo)
+67. Siddharth VP (Jivox)
+68. Warren Fernandes(Media.net)
+69. David Tam (Relay42)
+70. Rickey Davis (Flashtalking)
+71. Hillary Slattery (IAB Tech Lab) SORRY KLEBER!
+72. Arian Senior (IAB France)
+73. Maybelline Boon (Google Privacy Sandbox)
+74. Caleb Raitto (Google Chrome)
+75. Hari Krishna Bikmal (Google Ads)
+
+
+## Note taker: Manny Isu, Alonso Velasquez
+
+
+# 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/
+
+Contributions to this work are subject to W3C Community Contributor License Agreement (CLA) which can be found here: https://www.w3.org/community/about/process/cla/
+
+
+## Suggest agenda items here:
+
+
+
+* Discussion of Monday's "A new path for Privacy Sandbox on the web" news https://privacysandbox.com/news/privacy-sandbox-update/
+
+* Isaac Foster:
+ * Brief revisit the “coarse information sharing” thing, we had talked about setting up time but never did, all got too busy…can even answer here
+ * 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
+
+* Warren:
+ * [Addition of an analytics/reporting entity to enable centralised reporting · Issue #1115 · WICG/turtledove · GitHub](https://github.com/WICG/turtledove/issues/1115)
+
+* David Dabbs
+ * Low entropy client hints on TBS requests ([#1031](https://github.com/WICG/turtledove/issues/1031))
+ * Interest also in updateURL and real-time reporting postbacks.
+
+ * Request for `updateURL` processing to support the leaving/joining of negative interest groups via the [nascent header feature](https://github.com/WICG/turtledove/issues/896)
+(entered as issue [#1228](https://github.com/WICG/turtledove/issues/1228))
+
+ In the “shell IG” pattern a buyer joins an IG with a minimal, updateable attribute set. This centralizes and defers page latency inducing processing (computing and rendering IG content) to updateURL time. (Proposed) negative targeting adds a wrinkle. Scenario: a buyer intends to negatively target a positive interest group. The Chrome-suggested approach has the buyer pair the excluded positive IG with a targetable ‘negative shadow.’ At site visit time, the buyer joins the browser to “shells” of “EXCLUDED_POSITIVE,” the excluded IG, “EXCLUDED_NEGATIVE,” its negative ‘shadow,’ and “EXCLUDER,” an IG that seeks to negatively target the other group. EXCLUDER’s update will configure EXCLUDED_NEGATIVE in `negativeInterestGroups[]`. If EXCLUDED_POSITIVE’s update determines that IG membership condition doesn’t hold (or the update doesn’t run &c.), the buyer has a partial, incorrect IG set.
+
+ The buyer’s ability to join or leave EXCLUDED_NEGATIVE during EXCLUDED_POSITIVE’s update would smooth this wrinkle. The primary can ensure its ‘shadow’ is always appropriately joined when it is processed.
+
+ * Request for nascent header join/leave capability to support [fetchLater API](https://developer.chrome.com/blog/fetch-later-api-origin-trial)
+(entered as [comment](https://github.com/WICG/turtledove/issues/896#issuecomment-2233667864) on #896)
+
+ Chrome is [migrating](https://issues.chromium.org/issues/40236167) keep-alive request handling from the “renderer” (front-end) to the “browser” process (back-end). [Explainer](https://docs.google.com/document/d/1ZzxMMBvpqn8VZBZKnb7Go8TWjnrGcXuLS_USwVVRUvY/edit#). Attribution Reporting API (ARA) supports event and trigger header registrations on background requests, and this will move to the browser. ARA team has [extended that hook](https://issues.chromium.org/issues/40242339#comment48) so that fetchLater
API requests will also be able to set ARA headers. Requesting that Protected Audience header processing also support fetchLater
.
+
+ * K-Anonymity: According to the k-anon [doc](https://developers.google.com/privacy-sandbox/relevance/protected-audience-api/k-anonymity):
+ * _In Q1 2024, for up to 20% of Chrome Stable traffic, excluding Mode A and Mode B experimental traffic, we will begin to check k-anonymity with the same parameters._
+
+ _In Q3 2024, when the third-party cookie deprecation (3PCD) ramp-up process is planned to begin, k-anonymity will be checked for 100% of Chrome Stable traffic with the same parameters?_
+
+ * Will the experimental groups continue to be excluded?
+
+ (You are discussing the path forward with CMA, but any clarity on k-anon, which has been and I assume still is the next major PA privacy enforcement change to drop, will be welcome when you can provide it.)
+
+
+# Announcements
+
+The Microsoft Edge folks are holding every-second-Thursday meetings at this same hour to discuss their very similar "Ad Selection API" work. See https://github.com/WICG/privacy-preserving-ads/issues/50 for logistics.
+
+Everyone is responsible for checking the accuracy of the notes doc, especially the part with their own comments - so go back later and make sure the notes are accurate. You can even fix them on Github later!! :)
+
+
+# Notes
+
+
+## Discussion of Monday's "A new path for Privacy Sandbox on the web" news https://privacysandbox.com/news/privacy-sandbox-update/
+
+
+
+* [Kleber] Alex Cone will be discussing this blog post
+* [Alex Cone] This is a new experience for users over 3PC - giving the users a choice. Acknowledge there are a ton of questions. But for now, we do not have all the answers to the questions but we are taking this very seriously, and we are working through them internally to make sure we provide answers to things like timeline, UX, etc… and will share with you all when we have them
+* [Alex Cone] This team stays here, the APIs don’t go anywhere
+* [Brian May] Will the community invite to participate in the interaction around user choice and will there be a WICG for it?
+ * [Alex Cone] We do not have plans for that yet, and we are still in discussion around it with regulators. We do not have designs yet, and generally you can expect we continue seeking feedback
+ * [Kleber] The kind of things discussed in WICG calls or other W3C groups are cross browser standards. That does not include the UI for a browser and has never included the UI of a browser - user settings are not part of a WICG call. These are things that any browser reasonably calls the shots on their own and there is no reason for any interoperability. I do not expect anything about settings UI to show up here or any W3C forum
+* [Isaac] Curious to any extent you can comment - do you anticipate continuing to do some type of ramp to get stronger evidence of its performance (where it needs to get better)? I’ll decouple from 3PCD.
+ * [Alex Cone] Do you mean a ramp of how choice would work or…?
+ * I could see Chrome making a statement - the current 1% stays the way it is but rather than ramping to say 5% with no option to go back, mode B will become 5% with default 3PC off?
+ * [Alex Cone] We do not have a plan to announce that, but acknowledge that having more traffic to test with will be helpful
+* [From in-call chat: Anthony Yam]: is the 1% rolling back?
+ * [Alex Cone] You can expect to hear from us later. We do not have an answer right now
+* [Brian May] Can you give us any rough idea on how things will progress from here? Timeline?
+ * [Alex Cone] We are moving with haste on that, but a piece is the discussion with regulators. But I cannot give you a timeline for the timeline just yet.
+* [Pat MacCann] Will the gnatcatcher IP privatization work now be tied to this new commitment date? Should I read everything that was tied to deprecation tied to UX release
+ * [Alex Cone] Still working through those details right now
+* [B. McLeod Sims] My understanding is that work will continue to move forward on these APIs, is that correct?
+ * [Alex Cone] We are continuing to invest in the APIs both on the privacy and utility side
+ * [Kleber] These APIs are being added to the web platform. They are available today for folks who have 3PC turned on or have 3PC turned off. If we decide to make any change as to the availability of the APIs, you will know about it long before it happens. But for now, there is no reason to expect any change with respect to availability
+* [Luckey Harpley] One goal of PS was to unify Chrome and Android. Any comment to this?
+ * [Kleber] No announcement as to changes for Android
+* [Brian] Is the PS timeline going to be kept updated and used to communicate to the ecosystem?
+ * [Alex] In terms of how we continue to use ecosystem update, I cannot speak to that right now
+* [Isaac] Curious on Chrome’s commitment to making additional web standard changes to ARA?
+ * [Kleber] Every change we make to the web platform in Chrome is designed to be a change to the Web in general. Our goal is for everything we are working on to become part of web standards across browsers. Those who can remember the days of different browsers behaving differently can understand we don’t want to go back to a similar state.
+* [Harshad Mane] When will UX be presented to user and what will be the default position?
+ * [Alex Cone] This is something we are working through on what the experience will be
+ * [Harshad Mane] Will it be global or in stages during release?
+ * [Alex Cone] No plans to share at this point
+* [From in-call chat: Warren Fernandes]: is IP obfuscation still on the cards?
+ * [Kleber] The linked news release does mention something about IP… we are going to be introducing IP protection in Chrome incognito mode. No statements to make as to what will happen to IP protection in the future. There is an ongoing discussion about IP addresses and privacy in every browser globally. This W3C meeting, and W3C in general, is not the forum for it because IP address stuff operates at a different protocol layer: W3C is about stuff happening at the HTTP layer, while IP address discussions are happening at the IETF, where network protocols live. Not a whole lot to say about it here.
+* [David Dabbs]: Suggest that folks who have further questions on the announcement add their questions to this doc to be further addressed
+ * [Kleber]: given the nature of this doc, intended to be a log of the live discussion vs a discoverable document for people outside the call, may not be the most appropriate place for that.
+* [David Tam]: What can you say about the announcement and the Shared Storage API?
+ * [Kleber]: Recall the Shared Storage API is an API that allows cross-site writing of data, with specific output gates for the secure and private consumption of it like the Private Aggregation API. There is no effect on that API based on this announcement
+* [David Tam]: Currently, when a cookie sync call is made it is a redirect that calls a third party domain. If the cookie sits in shared storage then how do you read the cookie. Correction, I should have referred to CHIPS.
+ * [Kleber]: the behavior of cookies has been the same for the last 25 years. If the browser has 3rd-party cookies on, then they will continue to work the same way they have before. If the browser has 3p cookies off, that's also a setting that browsers have had for a long time.
+* [From in-call chat: Kevin Lee]: Hello folks, please post your questions about User Choice in the PS Dev Support repo:[ https://github.com/privacysandbox/privacy-sandbox-dev-support/issues](https://github.com/privacysandbox/privacy-sandbox-dev-support/issues). A new issue label "user-choice" has been created.
+ * [Kleber] If you have questions about the whole new path user choice cookie thing, there is a right place for them in the Privacy Sandbox developer support GitHub repository that Kevin linked to.
+
+
+## Discussion of: [Addition of an analytics/reporting entity to enable centralised reporting · Issue #1115 · WICG/turtledove · GitHub](https://github.com/WICG/turtledove/issues/1115)
+
+
+
+* [Warren F] Have you had a chance to go through the issue?
+ * [Paul] We discussed a few weeks ago and I had a chance to speak to the Shared Storage folks. Previously, we talked about who will be in charge of giving the information - we decided it will be the seller. Also, for header mechanism to facilitate that, it looks like you added that to the document. I wanted to talk to Josh (Shared Storage), and wondering the timing of it, how will it get permission? We envision it will get written to SS and how do we envision the SS worklet, say how will the publisher know when to publish and what timeframe?
+ * [Warren F] I think it is something the browser will do periodically. It is in section C of the document
+ * [Paul] Not an expert in SS. The browser does not do a whole lot of things periodically - it is hard for it to do so
+ * [Warren F] But it could be a similar mechanism as aggregate with some arbitrary delay. I think it will work fine no matter what the cadence is, but as long as it is not super infrequent
+ * [Patrick McCann] Publishers can call this, and on page, they can do a clean up and see if there are any events that they haven't called yet… the requirement is that some other entity will be able to generate this report.
+ * [Paul] I think SS is still the right proposal; the issue is the when - that is, when to run it
+ * [Kleber] I suspect the answer might run into a coordination problem with the publisher. But I think we should be able to find time to run it.
+* [Brian] It sounds like they may be a parallel with the trigger attribution call in ARA
+ * [Kleber] Not sure about that call
+ * [Paul] I think at the time Patrick was describing, not sure there is a network fetch for a header response. We generally do not make a request at page unload time
+* [Patrick McCann] There is [Event level reporting](https://github.com/WICG/shared-storage?tab=readme-ov-file#event-level-reporting) out of SS currently documented for 2026 - will that be moved forward? Should we still plan to build tooling on it?
+ * [Kleber] The flow we should build: Write to Shared Storage - Use existing output gate to do reporting. I do not think event-level reporting coming out of SS is a good fit for what you are trying to do - that's part of the selectURL output gate, and the event-level reporting is about which of the 8 possible URLs was selected, not about the contents of Shared Storage. The private aggregation is much more appropriate
+ * [Patrick] Makes sense. That answers it.
+* [Warren F] How do we move forward on this one since everyone is broadly okay with it?
+ * [Paul] We should formalize the proposal a bit more; explicitly stating names of the header and names of the APIs and make sure we are all onboard. If everyone is interested, we can then consider moving forward
+ * [Warren F] You can pick whatever you want as it’s just a name
+ * [Patrick M] I agree with Warren. Just make the decisions and preserve the momentum of this effort.
+ * [Paul] I think we may be at the point where we can propose something more formal given my preliminary review. The next step is to convert this into an Explainer and then we can make it a PR to the GH issue. I will either do this or find somebody on the Chrome side to do this one. Note that this is pretty complicated, so it might take me a while.
+
+[Kevin Lee]
+
+
+
+* Hello folks, please post your questions about User Choice in the PS Dev Support repo: https://github.com/privacysandbox/privacy-sandbox-dev-support/issues
+* A new issue label "user-choice" has been created.
diff --git a/meetings/2024-07-31-FLEDGE-call-minutes.md b/meetings/2024-07-31-FLEDGE-call-minutes.md
new file mode 100644
index 000000000..f1f618838
--- /dev/null
+++ b/meetings/2024-07-31-FLEDGE-call-minutes.md
@@ -0,0 +1,280 @@
+# 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 = 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 July 31, 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/
+
+If the meeting disappears from your calendar: try leaving and re-joining that group
+
+
+## Attendees: please sign yourself in!
+
+
+
+1. Michael Kleber (Google Privacy Sandbox)
+2. Brian May (Dstillery)
+3. Roni Gordon (Index Exchange)
+4. Paul Jensen (Google Privacy Sandbox)
+5. Omri Ariav (Taboola)
+6. Fabian Höring (Criteo)
+7. Harshad Mane (PubMatic)
+8. Tim Taylor (Flashtalking)
+9. David Dabbs (Epsilon)
+10. Anthony Yam (Flashtalking)
+11. Alex Cone (Google Privacy Sandbox)
+12. Denvinn Magsino (Magnite)
+13. Paul Spadaccini (Flashtalking)
+14. Becky Hatley (Flashtalking)
+15. Kevin Lee (Google Privacy Sandbox)
+16. Yanay Zimran (Start.io)
+17. Matt Kendall (Index Exchange)
+18. Isaac Foster (MSFT Ads)
+19. Felipe Gutierrez (MSFT Ads)
+20. Josh Singh (MSFT Ads)
+21. Laurentiu Badea (OpenX)
+22. Laura Morinigo (Samsung)
+23. Matt Davies (Bidswitch | Criteo)
+24. Tamara Yaeger (BidSwitch)
+25. Orr Bernstein (Google Privacy Sandbox)
+26. Brian Schneider (Google Privacy Sandbox)
+27. Herschel Wiseman(Google Privacy Sandbox)
+28. Arthur Coleman (IDPrivacy)
+29. Jeremy Bao (Google Privacy Sandbox)
+30. Pooja Muhuri (Google Privacy Sandbox)
+31. Alex Peckham (Flashtalking)
+32. Stan Belov (Google Ad Manager)
+33. Lydon, Jason (Flashtalking)
+34. Matthew Atkinson (Samsung)
+35. Bharat Rathi (Google Privacy sandbox)
+36. Andrey Prokofyev (Google Display & Video 360)
+37. Maybelline Boon (Google Privacy Sandbox)
+38. Sarah Harris (Flashtalking)
+39. Andrew Pascoe (NextRoll)
+40. Sathish Manickam (Google Privacy Sandbox)
+41. Abishai Gray (Google Privacy Sandbox)
+42. Ricardo Bentin (Media.net)
+43. Garrett McGrath (Magnite)
+44. Victor Pena (Google Privacy Sandbox)
+45. Shafir Uddin (Raptive)
+46. Alonso Velasquez (Google Privacy Sandbox)
+
+
+## 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/
+
+Contributions to this work are subject to W3C Community Contributor License Agreement (CLA) which can be found here: https://www.w3.org/community/about/process/cla/
+
+
+## Suggest agenda items here:
+
+
+
+* Isaac Foster:
+ * Brief revisit the “coarse information sharing” thing, we had talked about setting up time but never did, all got too busy…can even answer here
+ * 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
+
+* David Dabbs
+ * Request for nascent header join/leave capability to support [fetchLater API](https://developer.chrome.com/blog/fetch-later-api-origin-trial)
+(entered as [comment](https://github.com/WICG/turtledove/issues/896#issuecomment-2233667864) on #896)
+
+ Chrome is [migrating](https://issues.chromium.org/issues/40236167) keep-alive request handling from the “renderer” (front-end) to the “browser” process (back-end). [Explainer](https://docs.google.com/document/d/1ZzxMMBvpqn8VZBZKnb7Go8TWjnrGcXuLS_USwVVRUvY/edit#). Attribution Reporting API (ARA) supports event and trigger header registrations on background requests, and this will move to the browser. ARA team has [extended that hook](https://issues.chromium.org/issues/40242339#comment48) so that fetchLater
API requests will also be able to set ARA headers. Requesting that Protected Audience header join/leave capability also supports fetchLater
.
+
+ * K-Anonymity
+ According to the k-anon [doc](https://developers.google.com/privacy-sandbox/relevance/protected-audience-api/k-anonymity):
+ * _In Q1 2024, for up to 20% of Chrome Stable traffic, excluding Mode A and Mode B experimental traffic, we will begin to check k-anonymity with the same parameters._
+
+ _In Q3 2024, when the third-party cookie deprecation (3PCD) ramp-up process is planned to begin, k-anonymity will be checked for 100% of Chrome Stable traffic with the same parameters._
+ * Will the experimental groups continue to be excluded?
+
+ (You are no doubt discussing the path forward with CMA, but any clarity on k-anon, which has been and I assume still is the next major PA privacy enforcement change to drop, will be welcome when you can provide it.)
+
+* Roni Gordon: Update on the deals proposal
+ * https://github.com/WICG/turtledove/pull/1237/files, related to https://github.com/WICG/turtledove/issues/873#issuecomment-2196882982
+ * Timeline for implementation?
+
+
+# Announcements
+
+The Microsoft Edge folks are holding every-second-Thursday meetings at this same hour to discuss their very similar "Ad Selection API" work. See https://github.com/WICG/privacy-preserving-ads/issues/50 for logistics.
+
+Everyone is responsible for checking the accuracy of the notes doc, especially the part with their own comments - so go back later and make sure the notes are accurate. You can even fix them on Github later!! :)
+
+
+# Notes
+
+
+## Low entropy client hints on Trusted Bidding Signals requests ([#1031](https://github.com/WICG/turtledove/issues/1031))
+
+
+
+* David Dabbs
+ * Client hints on the Trusted Bidding Signals request
+ * Requesting an update on client hints on real-time reporting
+* Paul Jensen
+ * Didn’t see a lot of motivation as to why folks wanted it
+ * Read over the Trusted Key Value Server trust model
+ * [FLEDGE Key/Value service trust model](https://github.com/privacysandbox/protected-auction-services-docs/blob/main/key_value_service_trust_model.md)
+ * The basis of it is that there are three design principles
+ * Overall flow: “The browser needs to trust that the return value for each key will be based only on that key and the hostname, and that the server does no event-level logging and has no other side effects based on these requests.”
+ * Why would we want to send more data to it?
+* David
+ * Omri Ariav may be able to answer this
+* Omri Ariav
+ * Common use case for advertisers who have an operating system they want to target.
+* Michael Kleber
+ * The bid is calculated at a particular time, a bunch of different opportunities for information to flow into the environment where you calculate your bid. At IG join time, you get unrestricted access to all of the information about this particular user and site. Another time is the contextual flow of information into the bid at the auction that the auction is taking place. You already have control, know everything you want to know, at these two times. You can use this information to decide what ads to put into the IG, and again, if you want to do some sort of cross-checking between the IG time and auction join time, the contextual information from that side of the auction is an opportunity to do so. Maybe the Trusted Bidding Server is not the best place to talk about that becoming available. On the other hand, what I heard as other people were discussing this, is that this is a question not just about the TBS, but also about the periodic IG refresh, and there it’s a very different question.
+* David
+ * Suggestion you just made makes a great segue to that. Recent discussions of people pursuing the shell IG joining pattern, if you’re not making those decisions at join time and deferring that to the update and centralizing.
+* Michael
+ * You could, at the time you’re making an IG, you can annotate in that IG, e.g. in the IG name, you can make available to you for updates in the future, e.g. by including user agent.
+ * However, not much reason not to make those low-entropy signals available at update time.
+* Omri
+ * Wondering if it’s possible to have a formal explanation on the ticket.
+* Michael
+ * Yes, I can do that.
+* Omri
+ * Where would we put these signals in the IG? In the name of the IG? Or is there a field or object?
+* Paul
+ * Yes, I think that’s the place to put it.
+* Michael
+ * For the TBS, you can always include it in a key. Including it in the name of the IG is relevant as a workaround for what David mentioned about wanting to know that information at IG update time.
+* David Dabbs
+ * Or you could put it in the update URL. You have to build the update URL.
+* Michael
+ * Oops, yes, that’s absolutely right, update URL, not IG name.
+* Paul
+ * David, you were talking about update to the user agent client hints. What’s the user case for that one?
+* David
+ * Language header, the client hints on the version. Yes, we could bake that into the update URL. If we’re not targeting a certain version, we could have not joined the IG then. If it’s not just Chrome and the version - if it’s mobile or not - if you have a line item and you want to do the bid, or more in the IG about whether it’s a mobile device. You might be producing information like keys and other things that will impact other decisions later. Mobile device or other device - similar user case for real-time reporting, have opportunity to aggregate everything, can you see if the behavior is aberrant on a particular device.
+* Brian May
+ * Important to have some information about device capabilities that naturally comes through this channel.
+* David
+ * Want to emphasize that the ask here is solely low-entropy UACH. Not looking for the granular stuff.
+ * Last thing to follow up - not the original request in 1031, which we can’t abide - but should I open a separate request for the update URL? I think I already did comment on the real-time postbacks and you have said there’s already a request for that.
+* Brian
+ * I’d like to vote for having a separate issue so that people who are interested can find it.
+* David
+ * A separate one for each? A request to provide on updateURL and [real-time reporting] postbacks?
+* Paul
+ * Different ones. I haven’t looked at how easy to implement.
+* Michael
+ * We’ll have to do our due diligence.
+* Paul
+ * As Michael said, you can do it through the keys for trusted bidding signals or through the URL for updates.
+* David
+ * It does, but it adds to IG overhead, have to jam all that stuff into the update URL.
+* Michael
+ * Possible to work around it today, more convenient if it had direct native support directly from the browser. We generally put higher priority on feature requests that are not otherwise possible over things that are possible but may be awkward to do it today, so may not be the first thing on the queue.
+* David
+ * So, priority on real-time reporting postbacks because Chrome is in complete control there so API users have no work-around.
+* Brian
+ * Suggestion to capture these exceptions on how the browser normally communicates be called out.
+
+
+## Update on the Deals proposal: https://github.com/WICG/turtledove/pull/1237/files, related to https://github.com/WICG/turtledove/issues/873#issuecomment-2196882982
+
+
+
+* Roni Gordon
+ * Following up on gaps to the original proposal. I saw an explainer out there. Just wanted to make sure I’m connecting the dots. End of the changes, curious about when we can try it out.
+* Paul
+ * Have started work on implementation. In the coming weeks, it should be testable. I don’t think the first CL has landed yet, but we’re working on that right now.
+ * I did put up a PR to solidify that. I know, Roni, you’d asked for that, because we had Leeron’s original proposal and made some changes to that.
+ * If you’re looking closely, there were a couple of ergonomic renames of things, we’ll post on GitHub just to explain it a bit more, may not be worth getting into now. If you asked for a timeline for implementation, first change should be landing very soon, and I suspect all of it will be landing soon after that. We’ve been working on it for a couple of weeks. We’ll post back on the deals thing with the couple of ergonomic renames, and then when it’s available for testing in canary.
+* Michael
+ * As a reminder, things we land to Chrome make it very rapidly to Chrome Dev and Chrome Canary, and then once a month gets promoted to Chrome Beta channel. Chrome 129 is going to move to Beta on or around August 21; that's the earliest that something can land in Beta, but if you’re super eager to try things out before they get to Beta/Stable, try them out in Dev/Canary channels.
+* David
+ * You’d said the work is underway. Is there a buganizer item for that? Re the ergonomics, one thing that came up is, it looks like we’re putting “seat” in a particular field; is that the name of the field name?
+* Paul
+ * I don’t know if there’s a buganizer if you’re interested in following along on the CLs landing. In terms of naming things “deal” or “sealID”, we tend not to be proscriptive. We tend to build infrastructure, and people can use it however they want, can innovate from there.
+
+
+## Request for `updateURL` processing to support the leaving/joining of negative interest groups via the [nascent header feature](https://github.com/WICG/turtledove/issues/896)
+(entered as issue [#1228](https://github.com/WICG/turtledove/issues/1228))
+
+
+
+* David Dabbs
+ * Can we negatively target a regular interest group. Orr had said, why don’t we just create a shadow negative interest group? But it seems to be more ergonomic that, at update time, we could either join or leave an negative IG. The wrinkle I found is, I’m going to be joining some groups, put next to nothing in them, going to do all of the heavy lifting at update time. Want to be able to either add the negative shadow at update time, or leave it, so it’s effectively not impacting the IG that wants to anti-target it.
+* Michael
+ * The existing work that we’ve talked about in this group is equivalent header. Very much in line of Privacy Sandbox using HTTPheaders instead of needing to run JS. The “interest group header joining” may already be in progress?
+* David
+ * Met with Jeremy, seems there’s still some discussion about it.
+* Jeremy Bao
+ * Still making progress.
+* Michael
+ * You’re saying one particular request/response on which it’s helpful to have the header thing is the request/response on which one would join a negative interest group. One important property of an IG is that a page that a person is visiting when they join an IG - it’s an essential element of the nature of an IG, the time is also very important to an IG, related to the potential longevity of an IG. So the idea of joining of an IG when a person is not actually visiting a page - because it’s an HTTP response happening in the background - presents some problems.
+* David
+ * What about leaving an IG? That’s what I’m really interest in.
+* Michael
+ * If we’re just talking about leaving an IG, then it’s much easier to say yes to.
+* David
+ * Extra-territoriality of the IG update call. Deleting or leaving would smooth out the wrinkle. If I’ve deferred the hard decisions to later, but then I call the update for the one that I join on the shadow.
+* Michael
+ * Just to make sure I understand the flow you’re imagining. The shell IG flow you’re imagining. While the user is visiting the site, you make two different shell IG joins - one for regular IG, one for negative IG.
+* David
+ * Actually three. We’re closing on the opportunity to join lazily. The IG - excluded positive - that’s the one you really want (if you could anti-target it, you would); then the IG that wants to negatively target that. And then, you need that actual negative IG, that is the shadow of the one that also may exist. Turns out the second positive IG - you can’t join it - you need to delete its negative shadow, that the other groups may be negatively targeting. That’s why three or more.
+* Michael
+ * Almost makes sense. Let me repeat it back.
+ * What you’re envisioning. Instead of a single IG, you’re envisioning a flock of three IGs.
+ * IG X - functions like a regular old IG, that has a bunch of ads and potential bidding on those ads.
+ * Negative IG - which you add somebody to anytime you add them to IG X, which is an IG that serves that the user is in IG X, so that some other IG may choose to negatively target.
+ * IG that actually takes up that negative IG on its offer. Exactly the complement of the first positive IG. Going to show to people who are not in X.
+ * Person just joined X, so now you can target people in X and people not in X.
+* David
+ * And their status may change - may be updated.
+* Michael
+ * Not completely sold that it makes sense to join IGs in sets of three in the way you’re describing. You might often want to have negative IGs that are at a different granularity than just people not in a single IG. Not sold on the creation of a positive IG that negatively targets the negative IG, but nothing inherently wrong with how you’re describing using the flow.
+* David
+ * But that’s why Jeremy is expanding the positive IGs to be able to negatively target a negative interest group.
+* Jeremy Bao
+ * Proposal already out to allow negative targeting a PA bid. In the positive IG, you can specify the negative IG you want to anti-target. I’m trying to understand with your three group proposal, what’s the additional thing the current proposal cannot solve?
+* David
+ * At the join site, if you’re deferring the full evaluation of information until update time - what the shell approach entails - because of that, you need the three IGs.
+* Michael
+ * I’ll try again to repeat this back.
+ * If you don’t want to do any decision making about how to use IGs; you merely want to record events and use those events later, then, anytime you observe an event X, you propose creating two IGs
+ * One for ads you want to target that event X has happened
+ * And One for ads that want to target that event X has not happened
+ * And now you need to create three IGs - the negative IG, the regular IG that doesn’t negatively target the negative IG, and a regular IG that does negatively target the negative IG.
+ * Everything you’re talking about happens on this same site where user did event X. If you’re talking about a bunch of stuff that all happens on a single site, you only need one IG for all of that. This person is Nike customer 123456789, then you can send the fact that event X happened to that user, and then when you get your periodic daily update, you can make all the decisions you want at that point, and it does not require a large flock of interacting IGs.
+* David
+ * This is true, but the differentiator is that the positive IG that, in the ideal world, would be negatively targeted, is something that could be joined on many sites, but the one that’s actually negatively targeted, could only ever be joined on a single site.
+* Brian
+ * Jeremy - could you add a link to the proposal you mentioned?
+ * If we provide a capability for someone to do something, we need to provide a capability for them to undo that something if they did it by mistake. So, if we allow them to delete a negative IG, we should provide a capability to restore an accidentally deleted negative IG.
+* Jeremy (in chat)
+ * https://github.com/WICG/turtledove/issues/896
+* Paul
+ * Another way to phrase that - maybe we have a capability to disable that temporarily. Maybe remove the key.
+* David
+ * No way for the IG that’s negatively targeting the other one can know about its state or whether its active. It’s only the update to the positive IG that might be joined in various places that needs the negatively targeted shadow.
+* Michael
+ * What Paul is suggesting is that instead of deleting the negative IG, delete the key that makes that negative IG work, and then you’ve disabled the negative targeting capability.
+* David
+ * But they can’t have updateURLs.
+* Jeremy
+ * But if we allow negative IGs to be updated, that may make your use cases simpler?
+* Michael
+ * Will make the delete/undelete operation simpler.
+* Orr
+ * Indeed, negative IGs can’t be updated; was considered but decided against allowing update because it’s a lot of network traffic for a lightweight object that has no fields that typically need to be updated.
+* Brian
+ * If it can be turned off instead of destroyed, that would solve things for me. The use case I’m thinking about is, somebody doesn’t know what they’re doing, wipes out a subset of negative IG population, and we’d like to recover from this mistake.
diff --git a/meetings/2024-08-07-FLEDGE-call-minutes.md b/meetings/2024-08-07-FLEDGE-call-minutes.md
new file mode 100644
index 000000000..cf1ee99b2
--- /dev/null
+++ b/meetings/2024-08-07-FLEDGE-call-minutes.md
@@ -0,0 +1,246 @@
+# 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 = 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 Aug 7, 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/
+
+If the meeting disappears from your calendar: try leaving and re-joining that group
+
+
+## Attendees: please sign yourself in!
+
+
+
+1. Michael Kleber (Google Privacy Sandbox)
+2. Roni Gordon (Index Exchange)
+3. Pooja Muhuri (Google Privacy Sandbox)
+4. Paul Jensen (Google Privacy Sandbox)
+5. Orr Bernstein (Google Privacy Sandbox)
+6. David Dabbs (Epsilon)
+7. Matt Kendall (Index Exchange)
+8. Harshad Mane (PubMatic)
+9. Laura Morinigo (Samsung)
+10. Isaac Foster (MSFT Ads)
+11. Anthony Yam (Flashtalking)
+12. David Tam (paapi.ai formerly Relay42)
+13. Fabian Höring (Criteo)
+14. Kevin Lee (Google Privacy Sandbox)
+15. Alex Peckham (Flashtalking)
+16. Rickey Davis (Flashtalking)
+17. Sid Sahoo (Google Chrome)
+18. Lydon, Jason (fine… Flashtalking)
+19. Tamara Yaeger (BidSwitch)
+20. Sarah Harris (Flashtalking)
+21. Becky Hatley (Flashtalking)
+22. Owen Ridolfi (Flashtalking)
+23. Bharat Rathi [Google Privacy sandbox]
+24. Laurentiu Badea (OpenX)
+25. Jeremy Bao (Google Privacy Sandbox)
+26. Sathish Manickam (Google Privacy Sandbox)
+27. Laszlo Szoboszlai (Audigent)
+28. Koji Ota(CyberAgent)
+29. Paul Spadaccini (Flashtalking)
+30. Josh Singh (MSFT)
+31. Matthew Atkinson (Samsung)
+32. Shafir Uddin (Raptive)
+33. McLeod Sims (Media.net)
+
+
+## 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/
+
+Contributions to this work are subject to W3C Community Contributor License Agreement (CLA) which can be found here: https://www.w3.org/community/about/process/cla/
+
+
+## Suggest agenda items here:
+
+
+
+* Isaac Foster:
+ * Brief revisit the “coarse information sharing” thing, we had talked about setting up time but never did, all got too busy…can even answer here
+ * 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
+
+* David Dabbs
+ * Clarification on reporting values in Deals explainer:
+https://github.com/WICG/turtledove/pull/1237#discussion_r1700378453
+
+ * Request for nascent header join/leave capability to support [fetchLater API](https://developer.chrome.com/blog/fetch-later-api-origin-trial)
(entered as [comment](https://github.com/WICG/turtledove/issues/896#issuecomment-2233667864) on #896)
+
+ Chrome is [migrating](https://issues.chromium.org/issues/40236167) keep-alive request handling from the “renderer” (front-end) to the “browser” process (back-end). [Explainer](https://docs.google.com/document/d/1ZzxMMBvpqn8VZBZKnb7Go8TWjnrGcXuLS_USwVVRUvY/edit#). Attribution Reporting API (ARA) supports event and trigger header registrations on background requests, and this will move to the browser. ARA team has [extended that hook](https://issues.chromium.org/issues/40242339#comment48) so that fetchLater
API requests will also be able to set ARA headers. Requesting that Protected Audience header join/leave capability also supports fetchLater
.
+
+ * K-Anonymity
+ According to the k-anon [doc](https://developers.google.com/privacy-sandbox/relevance/protected-audience-api/k-anonymity):
+ * In Q1 2024, for up to 20% of Chrome Stable traffic, excluding Mode A and Mode B experimental traffic, we will begin to check k-anonymity with the same parameters.
+
+ In Q3 2024, when the third-party cookie deprecation (3PCD) ramp-up process is planned to begin, k-anonymity will be checked for 100% of Chrome Stable traffic with the same parameters.
+ * Will the experimental groups continue to be excluded?
+ (You are no doubt discussing the path forward with CMA, but any clarity on k-anon, which has been and I assume still is the next major PA privacy enforcement change to drop, will be welcome when you can provide it.)
+
+
+
+# Announcements
+
+The Microsoft Edge folks are holding every-second-Thursday meetings at this same hour to discuss their very similar "Ad Selection API" work. See https://github.com/WICG/privacy-preserving-ads/issues/50 for logistics.
+
+Everyone is responsible for checking the accuracy of the notes doc, especially the part with their own comments - so go back later and make sure the notes are accurate. You can even fix them on Github later!! :)
+
+
+# Notes
+
+
+## Clarification on reporting values in Deals explainer:
+https://github.com/WICG/turtledove/pull/1237#discussion_r1700378453
+
+
+
+* David Dabbs
+ * The question in that series of comments - the read on section 1.2 is that those reporting values are mutually exclusive - but Paul said that one would get all of them. What’s the availability?
+* Paul
+ * (Presents “Deals explainer” #1237)
+ * A bit trickier. Orr had a proposal for making it simpler, but can’t without changing existing behavior. There’s a complex English description of the behavior.
+ * Today, without deal support, in today’s behavior, there are three reporting IDs - IG name, buyerReportingId, buyerAndSellerReportingId. If you put buyerAndSellerReportingId, you get buyerAndSellerReportingId. If instead you put buyerReportingId, you get buyerReportingId. If you put neither, you get IG name. Whichever one you want to get through to reporting is passed through k-anon.
+ * Originally, we thought about using buyerAndSellerReportingId and making that an array. There are two problems with reusing an old field. Not a great idea when designing an API - confuses people who used it before. Also, an exciting JS behavior, when you pass an array to a string field in WebIDL, it stringifies the array and passes it through as a string.
+ * So, we decided to leave buyerAndSellerReportingId alone and create a new field, selectableBuyerAndSellerReportingIds. buyerAndSellerReportingId and buyerReportingId are still optional string fields.
+ * New selectableBuyerAndSellerReportingIds.
+ * Before, you picked one. New behavior is a little different. We want to pick the selected_buyerAndSellerReportingId - the one you pick in generateBid - but also to be able to allow ad techs to specify buyerAndSellerReportingId and buyerReportingId. buyerAndSellerReportingId is not selectable, but still gets passed to both buyer and seller. For deals use case, this could be the seat ID. You could take the seat ID and append it to every selectableBuyerAndSellerReportingIds element, but that would bloat your IG. buyerReportingId could be private information that you don’t want to report to the seller.
+ * So, when you’re using selectableBuyerAndSellerReportingIds, you can pass these in as well. This is different from the behavior when you don’t specify selectableBuyerAndSellerReportingIds - there, you still get only one of the fields. When you do pass selectableBuyerAndSellerReportingIds, you get all of the reporting fields you provided.
+ * I’m going to add a chart - it is complicated. We have a few charts internally to make sure we’re all on the same page. Going to add a chart to show which ones you get and which ones you don’t get.
+* David
+ * To your point of private information, if I had access to the k-anonymous - when those win, all of the ads that get served are going to be k-anonymous - but all of the information I need will be in the URL. I’ll need to understand how I’m going to get them out.
+ * The actual renderUrl is not available to reportWin because the expectation is - you’ve got a buyerReportingId - you can channel anything you want through that.
+* Michael
+ * No, the renderUrl is always available.
+
+
+## Request for nascent header join/leave capability to support [fetchLater API](https://developer.chrome.com/blog/fetch-later-api-origin-trial)
(entered as [comment](https://github.com/WICG/turtledove/issues/896#issuecomment-2233667864) on #896)
+
+
+
+* David Dabbs
+ * Forward looking. Attribution reporting API group has hooked to be able to trigger on background fetches. Putting in a request - when Jeremy and all puts in the feature for PA to process IG events or IG API activations via headers - to be able to have fetchLater fetches process PA header in the background. That’s another way to take PA related processing off the critical path on an advertise or other site.
+ * I requested this feature as a comment on #896 - negative targeting. This is where there was a lot of discussion about processing IG joins and leaves and whatever via headers. When we have that header processing, we want it on fetchLater.
+* Michael
+ * Can you clarify why you think that fetchLater is a good use case? As I understand it, the use case for fetchLater is - I want to know how long a user spends on my webpage - and I want to use a beacon to send it, but because I waited until the last second to send it, it’s possible that I don’t get my beacon. So, fetchLater is useful when I want to send a request just as a person is leaving the webpage.
+ * There’s nothing fundamentally I don’t like about this request, but I don’t understand why it makes sense.
+* David
+ * It’s retargeting. By virtue of visiting the site, you want to create an IG. I want to do this in the lightest possible way. Once there’s lightweight IG creation via headers, that’s how I prefer to do it. If it’s something in the background, then that’s how I’d want to do it.
+* Michael
+ * I don’t think fetchLater would be useful, since its goal is - don’t do this at all until the person is leaving the webpage. You could just issue a regular fetch.
+
+
+## K-Anonymity
+According to the k-anon [doc](https://developers.google.com/privacy-sandbox/relevance/protected-audience-api/k-anonymity):
+
+
+
+* _In Q1 2024, for up to 20% of Chrome Stable traffic, excluding Mode A and Mode B experimental traffic, we will begin to check k-anonymity with the same parameters.
+In Q3 2024, when the third-party cookie deprecation (3PCD) ramp-up process is planned to begin, k-anonymity will be checked for 100% of Chrome Stable traffic with the same parameters._
+* Will the experimental groups continue to be excluded?
+(You are no doubt discussing the path forward with CMA, but any clarity on k-anon, which has been and I assume still is the next major PA privacy enforcement change to drop, will be welcome when you can provide it.)
+* David Dabbs
+ * Still on this timeline?
+ * Experimental groups - will they still be sent in headers? Or will the test cohorts still be excluded from k-anonymity?
+* Michael Kleber
+ * To the second question:
+ * Can we do something so that k-anonymity no longer applies to actual bidding functions but rather to reporting functions, which is what we need for actual privacy properties.
+ * That’s a whole infrastructure change that we haven’t put effort into implementing. You’re quite right that we could do this without damaging the privacy properties of the PA flows.
+ * If it turns out that this is really important to somebody, please comment on the GitHub issue. Maybe because k-anonymity thresholds are low right now, it’s not clear how important or urgent this is to anybody.
+ * To to first question:
+ * The answer right now is that we expect things to progress exactly as we talked about. We expect to increase the amount of traffic subject to the k-anonymity constraint later this year, sometime in Q3 is what that document says, and we expect that to still be in the group that doesn’t have group A/B testing labels to it. We’ll be doing that ramp up in the traffic that isn’t in those groups. No changes; all status quo.
+
+
+## David Tam: Mode A/B traffic. How long will that stick around?
+
+
+
+* Michael Kleber
+ * We’d intended for this labeled traffic being available only during the first half of the year, during CMA testing, but there’s been some churn about how useful this labeling is.
+ * We know that we said so far that we’re going to end them in the end of July. We’ve extended them - with permission from Blink owners. They’re still a temporary thing. We’ll have to be patient a little longer to get resolution.
+* David Tam
+ * Limited amount of inventory that actually gets looked at by top-level seller.
+* Michael
+ * Question for GAM. The point of the labels and one of the reasons people have found them still useful is exactly so that there’s some traffic that everybody agrees that everybody expects a PA auction. My understanding is that GAM runs PA auctions outside of the labeled traffic. There’s two questions - one is how much traffic they’re running on, and how much traffic you can count their running on. The labeled traffic is the traffic you can count their running on, because they said they would do so. I don’t know what their future plans are.
+* David Tam
+ * How do I get an answer to this?
+* David Dabbs
+ * There’s a Google Ads experiments GitHub repository where they engage: try asking a question on https://github.com/google/ads-privacy
+* Michael
+ * Not sure if there are GAM folks on the calls. I don’t see any of their hands. GitHub or other forums - I don’t know if there are other forums, where people who are all experimenting with PA API are having conversations that I’m not a part of.
+* Fabian
+ * Speaking from the buyer perspective - two additional reasons why the labels are useful. Useful for A/B testing. And the second reason is to save infra cost - you can use PA on those populations.
+* Michael
+ * These are both issues we’re aware of, and understand it’s useful for the ad tech community to decide something based on whether people do or don't have 3P cookies in the future, and looking into how to provide that capability. Probably won’t be these labels in the future.
+
+
+## Negative targeting / headers
+
+
+
+* David Dabbs
+ * Jeremy - what’s your next steps on these? Explainer?
+* Jeremy Bao
+ * On the negative targeting for PA bids.
+ * Aren’t at the phase where we’re posting an explainer. Priority I’m currently working on.
+ * On the header bidding
+ * Not my priority yet
+* David Dabbs
+ * Doesn’t sound like you need more interest expressed for these, something the community wants.
+* Jeremy
+ * To make sure I get what you’re asking for - it’s allowing joinAdInterestGroup in HTTP response headers.
+* David Dabbs
+ * Yes, but with limits on IG creation. To paraphrase Michael - we (Chrome) could do that, but you can’t create a _whole_ IG. You could create a negative IG, which is lightweight, or a “shell IG”, which just has name, owner, updateURL, and minimal attributes - because it’s otherwise too long for a header. Is that right?
+* Michael
+ * Yes, there are technical limits on the size of an HTTP response header. Could use a header to create a small IG, and then use the update mechanism to fill it with ads and other attributes.
+* David Dabbs
+ * If it’s supported in the JS API, and it makes sense and it’s lightweight enough.
+ * The other thing I’m asking for re. Header API processing is for Chrome to support deleting IGs via a header on the updateURL response.
+* Michael
+ * If I recall the previous conversation we had about this, there’s some observation that - if your goal is to remove the IG, you could instead remove the cryptographic key, which is a field in the IG.
+* David Dabbs
+ * We covered that, but Orr pointed out that negative IGs are not updatable.
+* Michael
+ * Oh, that’s right, my mistake! So, what you’re saying, with the HTTP response header thing, in the updateUrl request, you want to be able to leave the IG that’s in the process of being updated, or being able to remove some other IG on the device, including possibly a negative IG.
+* David Dabbs
+ * That’s correct. Just remove an IG.
+ * Part of the scope of a header-based activation of those APIs.
+ * Jeremy - if you need a feature request, I could sketch that out.
+* Jeremy
+ * A feature request is still helpful. Different people asked for this feature for different reasons. Could you help write a feature?
+* David Dabbs
+ * Sure. To reiterate the conversation from a couple of weeks ago, I just want the expanded negative targeting to say, “I just want to negatively target this positive IG”.
+ * But Michael said that there are privacy reasons why this was not up for consideration.
+* Michael
+ * Not privacy - complexity. We already have positive IGs referencing negative IGs.
+ * Could reopen and change that, but as things work right now, you can only negatively target a negative IG.
+ * Do you have an answer for - what is the intended behavior if IG A negatively targets IG B which is on the browser, and IG negatively targets IG C, which is on the browser? Should IG A participate in the auction or not? The thing it’s looking for suppression is itself suppressed.
+* David Dabbs
+ * Didn’t give thoughts to transitivity.
+* Michael
+ * Having them be different things avoids the question. If you gave me an answer, I would then ask about what happens if there’s a cyclic chain. If we are going to rethink that restriction, we’ll need to get answers to all of these.
+* David Tam
+ * I was under the impression that there could only be one negative IG per positive IG.
+* David Dabbs
+ * Yes, In the current state, negative targeting with additional bids, you’re right. This convo has been talking to the expansion that Jeremy is proposing.
+* Jeremy
+ * Wrt issue #896 https://github.com/WICG/turtledove/issues/896, we’ll support negative targeting of PA bids - with a limit of 3. With negative targeting of additional bids, it’s also 3, though there’s a current discussion. There’s also a limit of 10 additional bids with negative targeting.
+* Michael
+ * If you have thoughts on how this feature ought to be used and how to appropriately give people enough flexibility to use them, please chime in on the issue.
+* David Dabbs
+ * I will take thoughts on these things and how they interact with the header mechanism and draft it into a document.
diff --git a/spec.bs b/spec.bs
index 6467757b1..81d016fdc 100644
--- a/spec.bs
+++ b/spec.bs
@@ -58,7 +58,8 @@ spec: RFC8941; urlPrefix: https://httpwg.org/specs/rfc8941.html
for: structured header
text: boolean; url: boolean
text: integer; url: integer
- text: boolean; url: boolean
+ text: list; url: list
+ text: string; url: string
spec: WebAssembly; urlPrefix: https://webassembly.github.io/spec/core/
type: dfn
urlPrefix: appendix/embedding.html
@@ -66,6 +67,9 @@ spec: WebAssembly; urlPrefix: https://webassembly.github.io/spec/core/
spec: WebAssembly-js-api; urlPrefix: https://webassembly.github.io/spec/js-api/
type: dfn
text: compiling a WebAssembly module; url: #compile-a-webassembly-module
+spec: WebAssembly-web-api; urlPrefix: https://webassembly.github.io/spec/web-api/
+ type: dfn
+ text: compiling a potential WebAssembly response; url: #compile-a-potential-webassembly-response
spec: WebIDL; urlPrefix: https://webidl.spec.whatwg.org/
type: dfn
text: convert a Web IDL arguments list to an ECMAScript arguments list; url: #web-idl-arguments-list-converting
@@ -80,6 +84,13 @@ spec: Shared Storage API; urlPrefix: https://wicg.github.io/shared-storage
type: dfn
text: shared-storage; url: #permissionspolicy-shared-storage
text: shared-storage-select-url; url: #permissionspolicy-shared-storage-select-url
+spec: CSP; urlPrefix: https://w3c.github.io/webappsec-csp/
+ type: dfn
+ text: directive name; url: directive-name
+ text: directive value; url: directive-value
+spec: CSPEE; urlPrefix: https://w3c.github.io/webappsec-cspee/
+ type: dfn
+ text: required csp; url: browsing-context-required-csp
@@ -300,21 +311,17 @@ This is detectable because it can change the set of fields that are read from th"{{GenerateBidInterestGroup/updateURL}}" [=interest group/update url=] -- 1. If |group| [=map/contains=] |groupMember|: - 1. Let |parsedUrl| be the result of running the [=URL parser=] on |group|[|groupMember|]. - 1. [=exception/Throw=] a {{TypeError}} if any of the following conditions hold: - * |parsedUrl| is failure; - * |parsedUrl| is not [=same origin=] with |interestGroup|'s [=interest group/owner=]; - * |parsedUrl| [=includes credentials=]; - * |parsedUrl| [=url/fragment=] is not null. + 1. Let |parsedUrl| be the result of running [=parse and verify a bidding code or update URL=] + on |group|[|groupMember|] and |interestGroup|'s [=interest group/owner=]. + 1. If |parsedUrl| is failure, then [=exception/throw=] a {{TypeError}}. 1. Set |interestGroup|'s |interestGroupField| to |parsedUrl|. - 1. If |interestGroup|'s [=interest group/trusted bidding signals url=]'s [=url/query=] is not - null, then [=exception/throw=] a {{TypeError}}. + 1. If |group|["{{GenerateBidInterestGroup/trustedBiddingSignalsURL}}"] [=map/exists=]: + 1. Let |parsedUrl| be the result of running [=parse and verify a trusted signals URL=] on + |group|[{{GenerateBidInterestGroup/trustedBiddingSignalsURL}}]. + 1. If |parsedUrl| is failure, then [=exception/throw=] a {{TypeError}}. + 1. Set |interestGroup|'s [=interest group/trusted bidding signals url=] to |parsedUrl|. 1. If |group|["{{GenerateBidInterestGroup/trustedBiddingSignalsKeys}}"] [=map/exists=], then set |interestGroup|'s [=interest group/trusted bidding signals keys=] to |group|["{{GenerateBidInterestGroup/trustedBiddingSignalsKeys}}"]. @@ -382,7 +389,7 @@ This is detectable because it can change the set of fields that are read from th * |group|["{{GenerateBidInterestGroup/updateURL}}"] [=map/exists=]. 1. Set |interestGroup|'s [=interest group/additional bid key=] to |decodedKey|. -1. If |interestGroup|'s [=interest group/estimated size=] > 1048576, then [=exception/throw=] a +1. If |interestGroup|'s [=interest group/estimated size=] > 1048576 bytes, then [=exception/throw=] a {{TypeError}}. 1. Let |p| be [=a new promise=]. 1. Let |queue| be the result of [=starting a new parallel queue=]. @@ -428,7 +435,7 @@ starting point, inspired by what the initial implementation of this specificatio [=interest group/owner=]. * Max interest groups total size per owner is"{{GenerateBidInterestGroup/trustedBiddingSignalsURL}}" -[=interest group/trusted bidding signals url=] -10\*1024\*1024
, which defines the max total [=interest group/estimated size|sizes=] of [=interest groups=] in the - [=user agent=]'s [=interest group set=] for an [=interest group/owner=]. It includs both + [=user agent=]'s [=interest group set=] for an [=interest group/owner=]. It includes both [=regular interest groups=] and [=negative interest groups=].@@ -472,7 +479,7 @@ To perform storage maintenance: 1. [=list/For each=] |owner| of |owners|: 1. Let |igs| be a [=list=] of [=interest groups=] in the [=user agent=]'s [=interest group set=] whose [=interest group/owner=] is |owner|, [=list/sorted in descending order=] with |a| being - less than |b| if |a|[=interest group/expiry=] comes before |b|[=interest group/expiry=]. + less than |b| if |a|'s [=interest group/expiry=] comes before |b|'s [=interest group/expiry=]. 1. Let |cumulativeSize| be 0. 1. [=list/For each=] |ig| of |igs|: 1. If the sum of |cumulativeSize| and |ig|'s [=interest group/estimated size=] @@ -524,7 +531,7 @@ dictionary AuctionAdInterestGroupKey { The leaveAdInterestGroup(group) method steps are: 1. Let |global| be [=this=]'s [=relevant global object=]. -1. Let |frameOrigin| be |global|'s [=environment settings object/origin=]. +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. If |group| [=map/is empty=]: @@ -628,6 +635,11 @@ bid in the auction for the chance to display their advertisement. [SecureContext] partial interface Navigator { Promise<(USVString or FencedFrameConfig)?> runAdAuction(AuctionAdConfig config); + readonly attribute boolean deprecatedRunAdAuctionEnforcesKAnonymity; +}; + +dictionary AuctionRealTimeReportingConfig { + required DOMString type; }; dictionary AuctionAdConfig { @@ -658,12 +670,25 @@ dictionary AuctionAdConfig { sequence> allSlotsRequestedSizes; Promise additionalBids; DOMString auctionNonce; + AuctionRealTimeReportingConfig sellerRealTimeReportingConfig; + record perBuyerRealTimeReportingConfig; sequence componentAuctions = []; AbortSignal? signal; Promise resolveToConfig; }; +Note: To help with ease of adoption, the browser will support the attribute +{{Window/navigator}}.{{Navigator/deprecatedRunAdAuctionEnforcesKAnonymity}} +while k-anonymity enforcement is being rolled out. This attribute is true +when {{Window/navigator}}.{{Navigator/runAdAuction()}} enforces +[[#k-anonymity]] when running an auction and false otherwise. The attribute +is not useful once k-anonymity enforcement is fully rolled out, and hence +this attribute will be deprecated and removed some time after this point. +See +https://developers.google.com/privacy-sandbox/relevance/protected-audience-api/k-anonymity for +more up to date information. + The runAdAuction(|config|) method steps are: @@ -698,14 +723,21 @@ The runAdAuction(|config|) method steps are: 1. Run [=update bid counts=] with |bidIgs|. 1. Run [=interest group update=] with |auctionConfig|'s [=auction config/interest group buyers=]. +1. Let |frameOrigin| be |settings|'s [=environment settings object/origin=]. +1. [=Assert=] that |frameOrigin| is not an [=opaque origin=] and its [=origin/scheme=] is "`https`". 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 |bidDebugReportInfoList| be a new [=list=] of [=bid debug reporting info=]. + 1. Let |realTimeContributionsMap| be a new [=real time reporting contributions map=]. 1. Let |winnerInfo| be the result of running [=generate and score bids=] with |auctionConfig|, - null, |global|, |settings|'s [=environment/top-level origin=], |bidIgs|, and |bidDebugReportInfoList|. + null, |global|, |settings|'s [=environment/top-level origin=], |bidIgs|, + |bidDebugReportInfoList|, and |realTimeContributionsMap|. 1. Let |auctionReportInfo| be a new [=auction report info=]. - 1. If |winnerInfo| is not failure, then set |auctionReportInfo| to the result of running - [=collect forDebuggingOnly reports=] with |bidDebugReportInfoList| and |winnerInfo|. + 1. If |winnerInfo| is not failure, then: + 1. Set |auctionReportInfo| to the result of running [=collect forDebuggingOnly reports=] with + |bidDebugReportInfoList| and |winnerInfo|. + 1. Set |auctionReportInfo|'s [=auction report info/real time reporting contributions map=] to + |realTimeContributionsMap|. 1. If |winnerInfo| is failure, then [=queue a global task=] on [=DOM manipulation task source=], given |global|, to [=reject=] |p| with a "{{TypeError}}". 1. Otherwise if |winnerInfo| is null or |winnerInfo|'s [=leading bid info/leading bid=] is null: @@ -713,11 +745,13 @@ The runAdAuction(|config|) method steps are: |p| with null. 1. [=list/For each=] |reportUrl| of |auctionReportInfo|'s [=auction report info/debug loss report urls=]: - 1. [=Send report=] to |reportUrl|. + 1. [=Send report=] with |reportUrl| and |frameOrigin|. + 1. [=Send real time reports=] with |auctionReportInfo|'s + [=auction report info/real time reporting contributions map=] and |frameOrigin|. 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 - |pendingConfig|, |auctionConfig|, |winnerInfo|, and |auctionReportInfo|. + |pendingConfig|, |auctionConfig|, |winnerInfo|, |auctionReportInfo|, and |frameOrigin|. 1. [=fenced frame config mapping/Finalize a pending config=] on |configMapping| with |urn| and |fencedFrameConfig|. 1. Wait until |auctionConfig|'s [=auction config/resolve to config=] is a boolean. @@ -807,8 +841,8 @@ To construct a pending fenced frame config given an [=auction config=To fill in a pending fenced frame config given a [=fenced frame config=] -|pendingConfig|, [=auction config=] |auctionConfig|, [=leading bid info=] |winningBidInfo|, and -[=auction report info=] |auctionReportInfo|: +|pendingConfig|, [=auction config=] |auctionConfig|, [=leading bid info=] |winningBidInfo|, +[=auction report info=] |auctionReportInfo|, and an [=origin=] |frameOrigin|: 1. Let |winningBid| be |winningBidInfo|'s [=leading bid info/leading bid=]. 1. Let |replacements| be an [=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are [=strings=]. @@ -879,8 +913,8 @@ To fill in a pending fenced frame config given a [=fenced frame confi 1. [=Asynchronously finish reporting=] with |pendingConfig|'s [=fenced frame config/fenced frame reporting metadata=]'s [=fenced frame reporting metadata/value=]'s - [=fenced frame reporting metadata/fenced frame reporting map=], |winningBidInfo| and - |auctionReportInfo|. + [=fenced frame reporting metadata/fenced frame reporting map=], |winningBidInfo|, + |auctionReportInfo| and |frameOrigin|. 1. Let |adComponentDescriptorsWithReplacements| be a new [=list=] of [=ad descriptors=]. 1. If |winningBid|'s [=generated bid/ad component descriptors=] is not null: 1. [=list/For each=] |adComponentDescriptor| of |winningBid|'s @@ -902,7 +936,7 @@ To fill in a pending fenced frame config given a [=fenced frame confi To asynchronously finish reporting given a [=fencedframetype/fenced frame reporting map=] |reportingMap|, [=leading bid info=] |leadingBidInfo|, -and [=auction report info=] |auctionReportInfo|. +[=auction report info=] |auctionReportInfo|, and an [=origin=] |frameOrigin|: 1. [=Increment a winning bid's k-anonymity count=] given |leadingBidInfo|'s [=leading bid info/leading bid=]. 1. If |leadingBidInfo|'s [=leading bid info/leading non-k-anon-enforced bid=] is not null, and |leadingBidInfo|'s [=leading bid info/leading non-k-anon-enforced bid=]'s [=generated bid/id=] @@ -928,8 +962,8 @@ and [=auction report info=] |auctionReportInfo|. [=reporting result/reporting macro map=]. 1. [=Finalize a reporting destination=] with |reportingMap|, {{FenceReportingDestination/buyer}}, |buyerMap|, and |macroMap|. - 1. [=Send report=] to |leadingBidInfo|'s [=leading bid info/buyer reporting result=]'s - [=reporting result/report url=]. + 1. [=Send report=] with |leadingBidInfo|'s [=leading bid info/buyer reporting result=]'s + [=reporting result/report url=] and |frameOrigin|. 1. Set |buyerDone| to true. 1. If |sellerDone| is false and |leadingBidInfo|'s [=leading bid info/seller reporting result=] is not null: @@ -938,8 +972,8 @@ and [=auction report info=] |auctionReportInfo|. 1. If |sellerMap| is null, set |sellerMap| to an empty [=map=] «[]». 1. [=Finalize a reporting destination=] with |reportingMap|, {{FenceReportingDestination/seller}}, and |sellerMap|. - 1. [=Send report=] to |leadingBidInfo|'s [=leading bid info/seller reporting result=]'s - [=reporting result/report url=]. + 1. [=Send report=] with |leadingBidInfo|'s [=leading bid info/seller reporting result=]'s + [=reporting result/report url=] and |frameOrigin|. 1. Set |sellerDone| to true. 1. If |componentSellerDone| is false and |leadingBidInfo|'s [=leading bid info/component seller reporting result=] is not null: @@ -949,13 +983,15 @@ and [=auction report info=] |auctionReportInfo|. 1. If |componentSellerMap| is null, set |componentSellerMap| to an empty [=map=] «[]». 1. [=Finalize a reporting destination=] with |reportingMap|, {{FenceReportingDestination/component-seller}}, and |componentSellerMap|. - 1. [=Send report=] to |leadingBidInfo|'s [=leading bid info/component seller reporting result=]'s - [=reporting result/report url=]. + 1. [=Send report=] with |leadingBidInfo|'s [=leading bid info/component seller reporting result=]'s + [=reporting result/report url=] and |frameOrigin|. 1. Set |componentSellerDone| to true. 1. [=list/For each=] |reportUrl| of |auctionReportInfo|'s [=auction report info/debug win report urls=]: - 1. [=Send report=] to |report|. + 1. [=Send report=] with |report| and |frameOrigin|. 1. [=list/For each=] |reportUrl| of |auctionReportInfo|'s [=auction report info/debug loss report urls=]: - 1. [=Send report=] to |report|. + 1. [=Send report=] with |report| and |frameOrigin|. +1. [=Send real time reports=] with |auctionReportInfo|'s + [=auction report info/real time reporting contributions map=] and |frameOrigin|.@@ -1049,11 +1085,9 @@ To validate and convert auction ad config given an {{AuctionAdConfig} [=auction config/seller=], then return failure. 1. Set |auctionConfig|'s [=auction config/decision logic url=] to |decisionLogicURL|. 1. If |config|["{{AuctionAdConfig/trustedScoringSignalsURL}}"] [=map/exists=]: - 1. Let |trustedScoringSignalsURL| be the result of running the [=URL parser=] on + 1. Let |trustedScoringSignalsURL| be the result of [=parse and verify a trusted signals URL=] on |config|["{{AuctionAdConfig/trustedScoringSignalsURL}}"]. - 1. If |trustedScoringSignalsURL| is failure, or it is not [=same origin=] with |auctionConfig|'s - [=auction config/seller=], then return failure. - 1. [=Assert=]: |trustedScoringSignalsURL|'s [=url/scheme=] is "`https`". + 1. If |trustedScoringSignalsURL| is failure, return failure. 1. Set |auctionConfig|'s [=auction config/trusted scoring signals url=] to |trustedScoringSignalsURL|. 1. If |config|["{{AuctionAdConfig/maxTrustedScoringSignalsURLLength}}"] [=map/exists=]: @@ -1312,6 +1346,16 @@ To validate and convert auction ad config given an {{AuctionAdConfig} failure, [=exception/throw=] a {{TypeError}}. 1. [=map/Set=] |auctionConfig|'s [=auction config/per buyer currencies=][|buyer|] to |value|. * To handle an error, set |auctionConfig|'s [=auction config/per buyer currencies=] to failure. +1. If |config|["{{AuctionAdConfig/sellerRealTimeReportingConfig}}"] [=map/exists=]: + 1. If |config|["{{AuctionAdConfig/sellerRealTimeReportingConfig}}"]["type"] is + "`default-local-reporting`", then set |auctionConfig|'s + [=auction config/seller real time reporting config=] to "`default-local-reporting`". +1. If |config|["{{AuctionAdConfig/perBuyerRealTimeReportingConfig}}"] [=map/exists=], + [=map/For each=] |key| → |value| of |config|["{{AuctionAdConfig/perBuyerRealTimeReportingConfig}}"]: + 1. Let |buyer| the result of [=parsing an https origin=] with |key|. + 1. If |buyer| is failure, then return failure. + 1. If |value|["type"] is "`default-local-reporting`", then set |auctionConfig|'s + [=auction config/per buyer real time reporting config=][|buyer|] to "`default-local-reporting`". 1. If |config|["{{AuctionAdConfig/componentAuctions}}"] is not [=list/empty=]: 1. If |config|["{{AuctionAdConfig/interestGroupBuyers}}"] [=map/exists=] and is not [=list/empty=], then return failure. @@ -1451,14 +1495,17 @@ To check if required seller capabilities are permitted given an [=auc-To generate potentially multiple bids given an [=ordered map=] |allTrustedBiddingSignals|, a [=string=] -|auctionSignals|, a {{BiddingBrowserSignals}} |browserSignals|, a [=string=]-or-null |perBuyerSignals|, +To generate potentially multiple bids given an [=ordered map=]-or-null +|allTrustedBiddingSignals|, and an [=origin=]-or-null |crossOriginTrustedBiddingSignalsOrigin|, +a [=string=] |auctionSignals|, a {{BiddingBrowserSignals}} |browserSignals|, a [=string=]-or-null |perBuyerSignals|, a {{DirectFromSellerSignalsForBuyer}} |directFromSellerSignalsForBuyer|, a [=duration=] |perBuyerTimeout| in milliseconds, a [=currency tag=] |expectedCurrency|, an {{unsigned short}} - -|multiBidLimit|, an [=interest group=] |ig|, and a [=moment=] |auctionStartTime|: - 1. Let |igGenerateBid| be the result of [=building an interest group passed to generateBid=] - with |ig|. +|multiBidLimit|, an [=interest group=] |ig|, and a [=moment=] |auctionStartTime|, and an +[=origin=] |frameOrigin|, perform the following steps. They return a failure if failing to fetch +the script or wasm, otherwise a [=tuple=] of ([=list=] of [=generated bids=], +[=bid debug reporting info=], [=list=] of [=real time reporting contributions=]). + 1. Let |igGenerateBid| be the result of [=building an interest group passed to generateBid=] with + |ig|. 1. Set |browserSignals|["{{BiddingBrowserSignals/joinCount}}"] to the sum of |ig|'s [=interest group/join counts=] for all days within the last 30 days. 1. Set |browserSignals|["{{BiddingBrowserSignals/recency}}"] to the [=current wall time=] @@ -1478,36 +1525,55 @@ a {{DirectFromSellerSignalsForBuyer}} |directFromSellerSignalsForBuyer|, a [=dur 1. [=map/Set=] |prevWinIDL|["{{PreviousWin/adJSON}}"] to |prevWin|'s [=previous win/ad json=]. 1. [=list/Append=] |prevWinIDL| to |prevWins|. 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/prevWinsMs}}"] to |prevWins|. - 1. Let |biddingScript| be the result of [=fetching script=] with |ig|'s - [=interest group/bidding url=]. + 1. Let |biddingScriptFetcher| be the result of [=creating a new script fetcher=] with + |ig|'s [=interest group/bidding url=], and |frameOrigin|. + 1. Let |biddingScript| be the result of [=waiting for script body from a fetcher=] given + |biddingScriptFetcher|. 1. If |biddingScript| is failure, return failure. 1. If |ig|'s [=interest group/bidding wasm helper url=] is not null: 1. Let |wasmModuleObject| be the result of [=fetching WebAssembly=] with |ig|'s - [=interest group/bidding wasm helper url=]. + [=interest group/bidding wasm helper url=] and |frameOrigin|. 1. If |wasmModuleObject| is not failure, then [=map/set=] |browserSignals|["{{BiddingBrowserSignals/wasmHelper}}"] to |wasmModuleObject|. - 1. Let |trustedBiddingSignals| be an [=ordered map=] whose [=map/keys=] are [=strings=] and - whose [=map/values=] are {{any}}. - 1. [=list/For each=] |key| of |ig|'s [=interest group/trusted bidding signals keys=]: - 1. If |allTrustedBiddingSignals| is an [=ordered map=] and |allTrustedBiddingSignals|[|key|] - [=map/exists=], then [=map/set=] |trustedBiddingSignals|[|key|] to - |allTrustedBiddingSignals|[|key|]. + 1. Otherwise, return failure. + 1. Let |trustedBiddingSignals| be null. + 1. If |allTrustedBiddingSignals| is not null: + 1. [=Assert=] that |ig|'s [=interest group/trusted bidding signals keys=] is not null. + 1. Set |trustedBiddingSignals| to an [=ordered map=] whose [=map/keys=] are [=strings=] and + whose [=map/values=] are {{any}}. + 1. [=list/For each=] |key| of |ig|'s [=interest group/trusted bidding signals keys=]: + 1. If |allTrustedBiddingSignals|[|key|] [=map/exists=], then [=map/set=] + |trustedBiddingSignals|[|key|] to |allTrustedBiddingSignals|[|key|]. + 1. Let |sameOriginTrustedBiddingSignals| be null. + 1. Let |crossOriginTrustedBiddingSignals| be null. + 1. If |trustedBiddingSignals| is not null: + 1. If |crossOriginTrustedBiddingSignalsOrigin| is null, then set + |sameOriginTrustedBiddingSignals| to |trustedBiddingSignals|. + 1. Otherwise: + 1. Set |crossOriginTrustedBiddingSignals| to a new [=ordered map=] whose [=map/keys=] are + [=strings=] and whose [=map/values=] are {{any}}. + 1. Let |originKey| be the [=serialization of an origin|serialization=] of + |crossOriginTrustedBiddingSignalsOrigin|. + 1. [=map/Set=] |crossOriginTrustedBiddingSignalsOrigin|[|originKey|] to |trustedBiddingSignals|. 1. Return the result of [=evaluating a bidding script=] with |biddingScript|, |multiBidLimit|, |ig|, |expectedCurrency|, - |igGenerateBid|, |auctionSignals|, |perBuyerSignals|, |trustedBiddingSignals|, |browserSignals|, - |directFromSellerSignalsForBuyer|, and |perBuyerTimeout|. + |igGenerateBid|, |auctionSignals|, |perBuyerSignals|, |sameOriginTrustedBiddingSignals|, + |crossOriginTrustedBiddingSignals|, |browserSignals|, |directFromSellerSignalsForBuyer|. + and |perBuyerTimeout|.To generate and score bids given an [=auction config=] |auctionConfig|, an [=auction config=]-or-null |topLevelAuctionConfig|, a [=global object=] |global|, an [=origin=] -|topLevelOrigin|, a [=list=] of [=interest groups=] |bidIgs|, and a [=list=] of [=bid debug reporting info=] -|bidDebugReportInfoList|: +|topLevelOrigin|, a [=list=] of [=interest groups=] |bidIgs|, a [=list=] of [=bid debug reporting info=] +|bidDebugReportInfoList|, and a [=real time reporting contributions map=] |realTimeContributionsMap|: 1. [=Assert=] that these steps are running [=in parallel=]. +1. Let |settings| be |global|'s [=relevant settings object=]. +1. Let |frameOrigin| be |settings|'s [=environment settings object/origin=]. +1. Let |seller| be |auctionConfig|'s [=auction config/seller=]. 1. Let |auctionStartTime| be the [=current wall time=]. -1. Let |decisionLogicScript| be the result of [=fetching script=] with |auctionConfig|'s - [=auction config/decision logic url=]. -1. If |decisionLogicScript| is failure, return null. +1. Let |decisionLogicFetcher| be the result of [=creating a new script fetcher=] with + |auctionConfig|'s [=auction config/decision logic url=] and |frameOrigin|. 1. Let |seller| be |auctionConfig|'s [=auction config/seller=]. 1. Let « |bidGenerators|, |negativeTargetInfo| » be the result of running [=build bid generators map=] with |auctionConfig|. @@ -1524,7 +1590,8 @@ 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 |compWinnerInfo| be the result of running [=generate and score bids=] with |component|, - |auctionConfig|, |global|, |topLevelOrigin|, |bidIgs|, and |bidDebugReportInfoList|. + |auctionConfig|, |global|, |topLevelOrigin|, |bidIgs|, |bidDebugReportInfoList|, and + |realTimeContributionsMap|. 1. If |compWinnerInfo| is failure, return failure. 1. If [=recursively wait until configuration input promises resolve=] given |auctionConfig| returns failure, return failure. @@ -1532,18 +1599,18 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. Let |topLevelDirectFromSellerSignals| be the result of running [=get direct from seller signals=] given |seller|, |auctionConfig|'s [=auction config/direct from seller signals header ad slot=], and |capturedAuctionHeaders|. - 1. Let |topLevelDirectFromSellerSignalsForSeller| be the result of running + 1. Set |topLevelDirectFromSellerSignalsForSeller| to the result of running [=get direct from seller signals for a seller=] given |topLevelDirectFromSellerSignals|. 1. Set |topLevelDirectFromSellerSignalsRetrieved| to true. 1. If |compWinnerInfo|'s [=leading bid info/leading bid=] is not null, then run [=score and rank a bid=] with |auctionConfig|, |compWinnerInfo|'s - [=leading bid info/leading bid=], |leadingBidInfo|, |decisionLogicScript|, + [=leading bid info/leading bid=], |leadingBidInfo|, |decisionLogicFetcher|, null, "top-level-auction", null, and |topLevelOrigin|. 1. If |compWinnerInfo|'s [=leading bid info/leading non-k-anon-enforced bid=] is not null, then run [=score and rank a bid=] with |auctionConfig|, |compWinnerInfo|'s [=leading bid info/leading non-k-anon-enforced bid=], - |leadingBidInfo|, |decisionLogicScript|, null, "top-level-auction", null, - and |topLevelOrigin|. + |leadingBidInfo|, |decisionLogicFetcher|, |topLevelDirectFromSellerSignalsForSeller|, null, + "top-level-auction", null, |topLevelOrigin| and |realTimeContributionsMap|.. 1. Decrement |pendingComponentAuctions| by 1. 1. Wait until |pendingComponentAuctions| is 0. 1. If |leadingBidInfo|'s [=leading bid info/leading bid=] is null, return null. @@ -1570,8 +1637,8 @@ To generate and score bids given an [=auction config=] |auctionConfig [=interest group/owner=]. 1. Let « |sellerSignals|, |reportResultBrowserSignals| » be the result of running [=report result=] with |leadingBidInfo|, |directFromSellerSignalsForSeller|, null, and |global|. - 1. Run [=report win=] with |leadingBidInfo|, |sellerSignals|, |reportResultBrowserSignals|, and - |directFromSellerSignalsForBuyer|. + 1. Run [=report win=] with |leadingBidInfo|, |sellerSignals|, |reportResultBrowserSignals|, + |directFromSellerSignalsForBuyer|, and |frameOrigin|. 1. Return |leadingBidInfo|. 1. If [=waiting until configuration input promises resolve=] given |auctionConfig| returns failure, @@ -1591,6 +1658,9 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/topWindowHostname}}"] to |topLevelHost|. 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/seller}}"] to the [=serialization of an origin|serialization=] of |seller|. +1. If |auctionConfig|'s [=auction config/requested size=] is not null, [=map/set=] + |browserSignals|["{{BiddingBrowserSignals/requestedSize}}"] to the result of running + [=convert an ad size to a map=] with |auctionConfig|'s [=auction config/requested size=]. 1. Let |auctionLevel| be "single-level-auction". 1. Let |componentAuctionExpectedCurrency| be null. 1. If |topLevelAuctionConfig| is not null: @@ -1606,10 +1676,9 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. Let |pendingAdditionalBids| be the [=list/size=] of |additionalBids|. 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 - |topLevelOrigin|. + |decisionLogicFetcher|, |directFromSellerSignalsForSeller|, null, |auctionLevel|, + |componentAuctionExpectedCurrency|, |topLevelOrigin| , and |realTimeContributionsMap|. 1. Decrement |pendingAdditionalBids| by 1. -1. Let |settings| be |global|'s [=relevant settings object=]. 1. [=map/For each=] |buyer| → |perBuyerGenerator| of |bidGenerators|, [=parallel queue/enqueue steps|enqueue the following steps=] to |queue|: 1. Let |perBuyerCumulativeTimeout| be |auctionConfig|'s @@ -1654,59 +1723,24 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. Let |multiBidLimit| be the result of [=looking up per-buyer multi-bid limit=] with |auctionConfig| and |buyer|. 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/forDebuggingOnlyInCooldownOrLockout}}"] to the result of running [=is debugging only in cooldown or lockout=] with |buyer|. + 1. Let |optedInForRealTimeReporting| be true if |auctionConfig|'s + [=auction config/per buyer real time reporting config=][|buyer|] is "`default-local-reporting`", + false otherwise. 1. [=map/For each=] |slotSizeQueryParam| → |perSlotSizeQueryParam| of |perBuyerGenerator|: 1. [=map/For each=] |signalsUrl| → |perSignalsUrlGenerator| of |perSlotSizeQueryParam|: - 1. Let |allTrustedBiddingSignals| be an [=ordered map=] whose [=map/keys=] are [=strings=], - and [=map/values=] are [=strings=]. - 1. Let |keys| be a new [=ordered set=]. - 1. Let |igNames| be a new [=ordered set=]. - 1. Let |lengthLimit| be the maximum value that a {{long}} integer can hold. + 1. Let |crossOriginTrustedBiddingSignalsOrigin| be null. + 1. If |buyer| is not [=same origin=] with |signalsUrl|'s [=url/origin=], then set + |crossOriginTrustedBiddingSignalsOrigin| to |signalsUrl|'s [=url/origin=]. + 1. Let |trustedBiddingSignalsBatcher| be a new [=trusted bidding signals batcher=]. 1. Let |fetchSignalStartTime| be |settings|'s [=environment settings object/current monotonic time=]. 1. [=map/For each=] joiningOrigin → |groups| of |perSignalsUrlGenerator|: - 1. Let |allPerInterestGroupData| be an [=ordered map=] whose [=map/keys=] are [=interest - group/name=] [=strings=] and whose [=map/values=] are [=bidding signals per interest group - data=]. 1. [=list/For each=] |ig| of |groups|: - 1. Let |putativeKeys| be a [=set/clone=] of |keys|. - 1. Let |putativeIgNames| be a [=set/clone=] of |igNames|. - 1. Let |putativeLengthLimit| be |ig|'s [=interest group/max trusted bidding signals url length=]. - 1. If |ig|'s [=interest group/max trusted bidding signals url length=] is 0 or `>` - |lengthLimit|, then set |putativeLengthLimit| to |lengthLimit|. - 1. [=list/Extend=] |putativeKeys| with |ig|'s [=interest group/trusted bidding signals keys=]. - 1. [=list/Append=] |ig|'s [=interest group/name=] to |putativeIgNames|. - - 1. Let |biddingSignalsUrl| be the result of [=building trusted bidding signals url=] with - |signalsUrl|, |putativeKeys|, |putativeIgNames|, |buyerExperimentGroupId|, |topLevelOrigin|, and - |slotSizeQueryParam|. - 1. If [=URL serializer|serialized=] |biddingSignalsUrl|'s [=string/length=] ≤ |putativeLengthLimit|: - - 1. Set |keys| to |putativeKeys|. - 1. Set |igNames| to |putativeIgNames|. - 1. Set |lengthLimit| to |putativeLengthLimit|. - 1. Otherwise: - 1. Let |biddingSignalsUrl| be the result of [=building trusted bidding signals url=] with - |signalsUrl|, |keys|, |igNames|, |buyerExperimentGroupId|, |topLevelOrigin|, and |slotSizeQueryParam|. - 1. Let « |partialTrustedBiddingSignals|, |partialPerInterestGroupData|, |dataVersion| » - be the result of [=fetching trusted signals=] with |biddingSignalsUrl| and true. - 1. [=Append to a bidding signals per-interest group data map=] with - |partialPerInterestGroupData|, |igNames|, and |allPerInterestGroupData|. - 1. [=map/For each=] |key| → |value| in |partialTrustedBiddingSignals|: - - 1. Set |allTrustedBiddingSignals|[|key|] to |value|. - 1. Set |keys| to a [=list/clone=] of |ig|'s [=interest group/trusted bidding signals keys=]. - 1. Set |igNames| to « |ig|'s [=interest group/name=] ». - 1. Set |ig|'s [=interest group/max trusted bidding signals url length=] to |lengthLimit|. - 1. Let |biddingSignalsUrl| be the result of [=building trusted bidding signals url=] with - |signalsUrl|, |keys|, |igNames|, |buyerExperimentGroupId|, |topLevelOrigin|, and |slotSizeQueryParam|. - 1. Let « |partialTrustedBiddingSignals|, |partialPerInterestGroupData|, |dataVersion| » be - the result of [=fetching trusted signals=] with |biddingSignalsUrl| and true. - 1. [=Append to a bidding signals per-interest group data map=] with - |partialPerInterestGroupData|, |igNames|, and |allPerInterestGroupData|. - 1. [=map/For each=] |key| → |value| in |partialTrustedBiddingSignals|: - 1. Set |allTrustedBiddingSignals|[|key|] to |value|. - 1. [=Process updateIfOlderThanMs=] with |buyer|, and |allPerInterestGroupData|. - 1. If |dataVersion| is not null, then [=map/set=] - |browserSignals|["{{BiddingBrowserSignals/dataVersion}}"] to |dataVersion|. + 1. [=Batch or fetch trusted bidding signals=] given |trustedBiddingSignalsBatcher|, + |ig|, |signalsUrl|, |buyerExperimentGroupId|, |topLevelOrigin|, and |slotSizeQueryParam|. + 1. [=Fetch the current outstanding trusted signals batch=] given |trustedBiddingSignalsBatcher|, + |signalsUrl|, |buyer|, |buyerExperimentGroupId|, |topLevelOrigin|, and |slotSizeQueryParam|. + 1. [=Process updateIfOlderThanMs=] with |buyer|, and |trustedBiddingSignalsBatcher|'s + [=trusted bidding signals batcher/all per interest group data=]. 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, then decrement |perBuyerCumulativeTimeout| by @@ -1722,10 +1756,36 @@ To generate and score bids given an [=auction config=] |auctionConfig 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 « |bidsBatch|, |bidDebugReportInfo| » be the result of [=generate potentially multiple bids=] given - |allTrustedBiddingSignals|, |auctionSignals|, a [=map/clone=] of |browserSignals|, - |perBuyerSignals|, |directFromSellerSignalsForBuyer|, |perBuyerTimeout|, - |expectedCurrency|, |multiBidLimit|, |ig|, and |auctionStartTime|. + 1. Let |dataVersion| be null. + 1. Let |allTrustedBiddingSignals| be an [=ordered map=]-or-null, initially null. + 1. Let |igName| be |ig|'s [=interest group/name=]. + 1. If |trustedBiddingSignalsBatcher|'s + [=trusted bidding signals batcher/no signals flags=][|igName|] does not [=map/exist=]: + 1. Set |dataVersion| to |trustedBiddingSignalsBatcher|'s + [=trusted bidding signals batcher/data versions=][|igName|]. + 1. Set |allTrustedBiddingSignals| to [=trusted bidding signals batcher/all trusted bidding signals=]. + 1. Otherwise if |trustedBiddingSignalsBatcher|'s + [=trusted bidding signals batcher/no signals flags=][|igName|] is "fetch-failed", and + |optedInForRealTimeReporting| is true, then: + 1. [=Add a platform contribution=] with [=trusted bidding signals failure bucket=], + |realTimeContributionsMap|, and |igName|. + 1. [=map/Remove=] |browserSignals|["{{BiddingBrowserSignals/dataVersion}}"]. + 1. [=map/Remove=] |browserSignals|["{{BiddingBrowserSignals/crossOriginDataVersion}}"]. + 1. If |dataVersion| is not null: + 1. If |crossOriginTrustedBiddingSignalsOrigin| is not null, then [=map/set=] + |browserSignals|["{{BiddingBrowserSignals/crossOriginDataVersion}}"] to |dataVersion|. + 1. Otherwise, [=map/set=] |browserSignals|["{{BiddingBrowserSignals/dataVersion}}"] to + |dataVersion|. + 1. Let « |bidsBatch|, |bidDebugReportInfo| » be the result of + [=generate potentially multiple bids=] given |allTrustedBiddingSignals|, + |crossOriginTrustedBiddingSignalsOrigin|, |auctionSignals|, a [=map/clone=] of + |browserSignals|, |perBuyerSignals|, |directFromSellerSignalsForBuyer|, |perBuyerTimeout|, + |expectedCurrency|, |multiBidLimit|, |ig|, |auctionStartTime|, and |frameOrigin|. + 1. If |generateBidResult| is failure, then: + 1. If |optedInForRealTimeReporting| is true, then [=add a platform contribution=] with + [=bidding script failure bucket=], |realTimeContributionsMap| and |buyer|. + 1. [=iteration/Continue=]. + 1. Let (|bidsBatch|, |bidDebugReportInfo|, |realTimeContributions|) be |generateBidResult|. 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 @@ -1762,10 +1822,11 @@ To generate and score bids given an [=auction config=] |auctionConfig |perBuyerTimeout| to |perBuyerCumulativeTimeout|. 1. Let |generateBidStartTime| be |settings|'s [=environment settings object/current monotonic time=]. - 1. Set « |generatedBids|, |bidDebugReportInfo| » to the result of [=generate potentially multiple bids=] given - |allTrustedBiddingSignals|, |auctionSignals|, a [=map/clone=] of |browserSignals|, + 1. Set (|generatedBids|, |bidDebugReportInfo|, |realTimeContributions|) to the result + of running [=generate potentially multiple bids=] with |allTrustedBiddingSignals|, + |crossOriginTrustedBiddingSignalsOrigin|, |auctionSignals|, a [=map/clone=] of |browserSignals|, |perBuyerSignals|, |directFromSellerSignalsForBuyer|, |perBuyerTimeout|, |expectedCurrency|, - 1 (for multiBidLimit), |ig|, and |auctionStartTime|. + 1 (for multiBidLimit), |ig|, |auctionStartTime|, and |frameOrigin|. Note: passing 1 for multiBidLimit limits the rerun to producing at most a single bid. @@ -1782,12 +1843,15 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. [=list/Append=] |generatedBid| to |bidsToScore|. 1. [=Register bids for forDebuggingOnly reports=] given |bidsToScore|, |bidDebugReportInfo|, and |bidDebugReportInfoList|. + 1. If |auctionConfig|'s [=auction config/per buyer real time reporting config=][|buyer|] + is "`default-local-reporting`", then [=insert entries to map=] given + |realTimeContributionsMap|, |buyer|, and |realTimeContributions|. 1. [=list/For each=] |bidToScore| of |bidsToScore|: 1. If |bidToScore|'s [=generated bid/for k-anon auction=] is true, [=list/append=] |bidToScore|'s [=generated bid/interest group=] to |bidIgs|. 1. [=Score and rank a bid=] with |auctionConfig|, |bidToScore|, |leadingBidInfo|, - |decisionLogicScript|, |directFromSellerSignalsForSeller|, |dataVersion|, |auctionLevel|, - |componentAuctionExpectedCurrency|, and |topLevelOrigin|. + |decisionLogicFetcher|, |directFromSellerSignalsForSeller|, |dataVersion|, |auctionLevel|, + |componentAuctionExpectedCurrency|, |topLevelOrigin|, and |realTimeContributionsMap|. 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. @@ -1798,18 +1862,23 @@ To generate and score bids given an [=auction config=] |auctionConfig [=get direct from seller signals for a buyer=] with |directFromSellerSignals|, and |leadingBidInfo|'s [=leading bid info/leading bid=]'s [=generated bid/interest group=]'s [=interest group/owner=]. - 1. Run [=report win=] with |leadingBidInfo|, |sellerSignals|, |reportResultBrowserSignals|, and - |directFromSellerSignalsForWinner|. + 1. Run [=report win=] with |leadingBidInfo|, |sellerSignals|, |reportResultBrowserSignals|, + |directFromSellerSignalsForWinner|, and |frameOrigin|. 1. Let |replacements| be an [=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are [=strings=]. - 1. [=list/For each=] [=ad keyword replacement=], |replacement|, within [=auction config/deprecated render url replacements=]: + 1. [=list/For each=] [=ad keyword replacement=], |replacement|, within + [=auction config/deprecated render url replacements=]: 1. Let |k| be |replacement|'s [=ad keyword replacement/match=]. 1. Let |v| be |replacement|'s [=ad keyword replacement/replacement=]. 1. [=map/Set=] |replacements|[|k|] to |v|. -1. Set |leadingBidInfo|'s [=leading bid info/leading bid=]'s [=generated bid/ad descriptor=] to the result of [=fencedframeutil/substitute macros=] with |replacements| and [=leading bid info/leading bid=]'s [=generated bid/ad descriptor=]. +1. Set |leadingBidInfo|'s [=leading bid info/leading bid=]'s [=generated bid/ad descriptor=] to the + result of [=fencedframeutil/substitute macros=] with |replacements| and [=leading bid info/leading bid=]'s + [=generated bid/ad descriptor=]. 1. If |leadingBidInfo|'s [=leading bid info/leading bid=]'s [=generated bid/ad descriptors=] is not null: - 1. [=list/For each=] [=generated bid/ad descriptor=], |adDescriptor|, within [=leading bid info/leading bid=]'s [=generated bid/ad descriptors=]: - 1. Set |adDescriptor| to the result of [=fencedframeutil/substitute macros=] with |replacements| and |adDescriptor|. + 1. [=list/For each=] [=generated bid/ad descriptor=], |adDescriptor|, within + [=leading bid info/leading bid=]'s [=generated bid/ad descriptors=]: + 1. Set |adDescriptor| to the result of [=fencedframeutil/substitute macros=] with |replacements| + and |adDescriptor|. 1. Return |leadingBidInfo|.@@ -1826,7 +1895,7 @@ To build an interest group passed to generateBid given an [=interest{{GenerateBidInterestGroup/enableBiddingSignalsPrioritization}} |ig|'s [=interest group/enable bidding signals prioritization=] {{GenerateBidInterestGroup/priorityVector}} - |ig|'s [=interest group/priority vector=] if not null, otherwise {{undefined}} + |ig|'s [=interest group/priority vector=] if not null, otherwise not set. {{GenerateBidInterestGroup/executionMode}} |ig|'s [=interest group/execution mode=] {{GenerateBidInterestGroup/biddingLogicURL}} @@ -1838,7 +1907,7 @@ To build an interest group passed to generateBid given an [=interest {{GenerateBidInterestGroup/trustedBiddingSignalsURL}} The [=serialize a URL|serialization=] of |ig|'s [=interest group/trusted bidding signals url=] {{GenerateBidInterestGroup/trustedBiddingSignalsKeys}} - |ig|'s [=interest group/trusted bidding signals keys=] + |ig|'s [=interest group/trusted bidding signals keys=], if not null, otherwise not set. {{GenerateBidInterestGroup/trustedBiddingSignalsSlotSizeMode}} |ig|'s [=interest group/trusted bidding signals slot size mode=] {{GenerateBidInterestGroup/maxTrustedBiddingSignalsURLLength}} @@ -1871,61 +1940,108 @@ To convert to an AuctionAd sequence given a [=list=]-or-null |ads|: -To score and rank a bid given an [=auction config=] |auctionConfig|, a [=generated bid=] -|generatedBid|, a [=bid debug reporting info=] |bidDebugReportInfo|, a [=leading bid info=] |leadingBidInfo|, a [=string=] |decisionLogicScript|, a -{{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=] -|topLevelOrigin|: - +To fetch and decode trusted scoring signals given an [=auction config=] |auctionConfig|, +a [=generated bid=] |generatedBid|, a [=script fetcher=] |decisionLogicFetcher|, an [=origin=] +|topLevelOrigin|, and a [=real time reporting contributions map=] |realTimeContributionsMap|: + +1. Let |crossOriginTrustedScoringSignalsOrigin| be null. +1. Let |sameOriginTrustedScoringSignals| be null. +1. Let |crossOriginTrustedScoringSignals| be null. +1. Let |scoringDataVersion| be null. 1. Let |renderURL| be [=URL serializer|serialized=] |generatedBid|'s [=generated bid/ad descriptor=]'s [=ad descriptor/url=]. 1. Let |adComponentRenderURLs| be a new empty [=list=]. 1. If |generatedBid|'s [=generated bid/ad component descriptors=] is not null: - 1. [=list/For each=] |adComponentDescriptor| in |generatedBid|'s - [=generated bid/ad component descriptors=]: + 1. [=list/For each=] |adComponentDescriptor| in |generatedBid|'s [=generated bid/ad component + descriptors=]: 1. [=list/Append=] [=URL serializer|serialized=] |adComponentDescriptor|'s [=ad descriptor/url=] 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 |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 - of [=building trusted scoring signals url=] to get a |scoringSignalsUrl|. By employing this approach, - the [=string/length=] of [=URL serializer|serialized=] |scoringSignalsUrl| must not exceed the - - [=auction config/max trusted scoring signals url length=] of the auction. In cases where the length - limit is exceeded, the request must be divided into smaller pieces to comply with the length restriction. - However, note that a single request is always considered valid, regardless of whether its length exceeds - the auction's [=auction config/max trusted scoring signals url length=]. - - - The network response has to be parsed to pull out the pieces relevant to each - [=evaluating a scoring script|evaluation of a scoring script=]. -1. Let |trustedScoringSignals| be null. -1. Let «|allTrustedScoringSignals|, ignored, |scoringDataVersion|» be the result of [=fetching - trusted signals=] with |fullSignalsUrl| and false. -1. If |allTrustedScoringSignals| is an [=ordered map=]: - 1. Set |trustedScoringSignals| to a new empty [=map=]. - 1. [=map/Set=] |trustedScoringSignals|["`renderURL`"] to a new empty [=map=]. - 1. If |allTrustedScoringSignals|["`renderURLs`"] [=map/exists=] and - |allTrustedScoringSignals|["`renderURLs`"][|renderURL|] [=map/exists=], then [=map/set=] |trustedScoringSignals|["`renderURL`"][|renderURL|] to - |allTrustedScoringSignals|["`renderURLs`"][|renderURL|]. - 1. If |adComponentRenderURLs| is not [=list/empty=]: - 1. Let |adComponentRenderURLsValue| be a new empty [=map=]. - 1. If |allTrustedScoringSignals|["`adComponentRenderURLs`"] [=map/exists=], [=set/for each=] - |adComponentRenderURL| in |adComponentRenderURLs|: - 1. If |allTrustedScoringSignals|["`adComponentRenderURLs`"][|adComponentRenderURL|] - [=map/exists=], then [=map/set=] |adComponentRenderURLsValue|[|adComponentRenderURL|] to - |allTrustedScoringSignals|["`adComponentRenderURLs`"][|adComponentRenderURL|]. - 1. [=map/Set=] |trustedScoringSignals|["`adComponentRenderURLs`"] to |adComponentRenderURLsValue|. +1. Let |fullSignalsUrl| be null. +1. If |auctionConfig|'s [=auction config/trusted scoring signals url=] is not null: + 1. Set |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 |topLevelOrigin|. + + Implementations may batch trusted scoring signals + requests with same [=auction config/trusted scoring signals url=], |auctionConfig|'s + [=auction config/seller experiment group id=], and |topLevelOrigin| 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 of [=building trusted scoring signals url=] to get a + |scoringSignalsUrl|. Requests may not be combined if the resulting combination's + [=string/length=] of [=URL serializer|serialized=] |scoringSignalsUrl| exceeds the [=auction + config/max trusted scoring signals url length=] of the auction; however this limit does not + apply if no combining has taken place. + + The network response has to be parsed to pull out the pieces relevant to each + [=evaluating a scoring script|evaluation of a scoring script=]. + + These requests may also begin before the script fetch, but requests cross-origin to the + script origin must not happen until [:Ad-Auction-Allow-Trusted-Scoring-Signals-From:] header on + the script is received, parsed, and determined to authorize such a fetch. + 1. If |fullSignalsUrl|'s [=url/origin=] is not [=same origin=] with |auctionConfig|'s + [=auction config/seller=], then: + 1. Set |crossOriginTrustedScoringSignalsOrigin| to |fullSignalsUrl|'s [=url/origin=]. + 1. Let |allowCrossOriginTrustedScoringSignalsFrom| be the result of [=wait for cross origin + trusted scoring signals authorization from a fetcher=] given |decisionLogicFetcher|. + 1. If |allowCrossOriginTrustedScoringSignalsFrom| does not [=list/contain=] + |crossOriginTrustedScoringSignalsOrigin|: + 1. Set |crossOriginTrustedScoringSignalsOrigin| to null. + 1. Set |fullSignalsUrl| to null. +1. Let |seller| be |auctionConfig|'s [=auction config/seller=]. +1. If |fullSignalsUrl| is not null: + 1. Let |allTrustedScoringSignals| be null. + 1. Set «|allTrustedScoringSignals|, + ignored, |scoringDataVersion|» to the result of [=fetching trusted signals=] + with |fullSignalsUrl|, |auctionConfig|'s [=auction config/seller=], and false. + 1. If |allTrustedScoringSignals| is null, and |auctionConfig|'s + [=auction config/seller real time reporting config=] is "`default-local-reporting`",then: + 1. [=Add a platform contribution=] with [=trusted scoring signals failure bucket=], + |realTimeContributionsMap|, and |seller|. + 1. Otherwise if |allTrustedScoringSignals| is an [=ordered map=]: + 1. Let |trustedScoringSignals| be a new empty [=map=]. + 1. [=map/Set=] |trustedScoringSignals|["`renderURL`"] to a new empty [=map=]. + 1. If |allTrustedScoringSignals|["`renderURLs`"] [=map/exists=] and + |allTrustedScoringSignals|["`renderURLs`"][|renderURL|] [=map/exists=], then [=map/set=] + |trustedScoringSignals|["`renderURL`"][|renderURL|] to + |allTrustedScoringSignals|["`renderURLs`"][|renderURL|]. + 1. If |adComponentRenderURLs| is not [=list/empty=]: + 1. Let |adComponentRenderURLsValue| be a new empty [=map=]. + 1. If |allTrustedScoringSignals|["`adComponentRenderURLs`"] [=map/exists=], [=set/for each=] + |adComponentRenderURL| in |adComponentRenderURLs|: + 1. If |allTrustedScoringSignals|["`adComponentRenderURLs`"][|adComponentRenderURL|] + [=map/exists=], then [=map/set=] |adComponentRenderURLsValue|[|adComponentRenderURL|] to + |allTrustedScoringSignals|["`adComponentRenderURLs`"][|adComponentRenderURL|]. + 1. [=map/Set=] |trustedScoringSignals|["`adComponentRenderURLs`"] to |adComponentRenderURLsValue|. + 1. If |crossOriginTrustedScoringSignalsOrigin| is null, set |sameOriginTrustedScoringSignals| + to |trustedScoringSignals|. + 1. Otherwise: + 1. Set |crossOriginTrustedScoringSignals| to a new [=map=]. + 1. Let |originKey| be the [=serialization of an origin|serialization=] given + |crossOriginTrustedScoringSignalsOrigin|. + 1. [=map/Set=] |crossOriginTrustedScoringSignals|[|originKey|] to |trustedScoringSignals|. +1. Return «|crossOriginTrustedScoringSignalsOrigin|, |sameOriginTrustedScoringSignals|, + |crossOriginTrustedScoringSignals|, |scoringDataVersion|» + ++ ++To score and rank a bid given an [=auction config=] |auctionConfig|, a [=generated bid=] +|generatedBid|, a [=bid debug reporting info=] |bidDebugReportInfo|, a [=leading bid info=] |leadingBidInfo|, +a [=script fetcher=] |decisionLogicFetcher|, a {{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|, an [=origin=] |topLevelOrigin|, and a +[=real time reporting contributions map=] |realTimeContributionsMap|: + +1. Let «|crossOriginTrustedScoringSignalsOrigin|, |sameOriginTrustedScoringSignals|, + |crossOriginTrustedScoringSignals|, |scoringDataVersion|» be the result of [=fetch and + decode trusted scoring signals=] given |auctionConfig|, |generatedBid|, |decisionLogicFetcher|, + |topLevelOrigin|, and |realTimeContributionsMap|. 1. Let |adMetadata| be |generatedBid|'s [=generated bid/ad=]. 1. Let |bidValue| be |generatedBid|'s [=generated bid/bid=]. 1. If |generatedBid|'s [=generated bid/modified bid=] is not null, then set |bidValue| to |generatedBid|'s [=generated bid/modified bid=]. 1. Let |owner| be |generatedBid|'s [=generated bid/interest group=]'s [=interest group/owner=]. -1. Let |seller| be |auctionConfig|'s [=auction config/seller=]. 1. Let |browserSignals| be a {{ScoringBrowserSignals}} with the following fields:-1. Let « |scoreAdResult|, |debugWinReportUrl|, |debugLossReportUrl| » be the result of - [=evaluating a scoring script=] with |decisionLogicScript|, |adMetadata|, +1. Let |decisionLogicScript| be the result of [=wait for script body from a fetcher=] given + |decisionLogicFetcher|. +1. If |decisionLogicScript| is failure, then: + 1. If |auctionConfig|'s [=auction config/seller real time reporting config=] is + "`default-local-reporting`", then [=add a platform contribution=] with + [=scoring script failure bucket=], |realTimeContributionsMap| and |seller|. + 1. Return. +1. Let « |scoreAdResult|, |debugWinReportUrl|, |debugLossReportUrl|, |realTimeContributions| » be + the result of [=evaluating a scoring script=] with |decisionLogicScript|, |adMetadata|, |bidValue|'s [=bid with currency/value=], |auctionConfig|'s [=auction config/config idl=], - |trustedScoringSignals|, |browserSignals|, |directFromSellerSignalsForSeller|, and - |auctionConfig|'s [=auction config/seller timeout=]. + |sameOriginTrustedScoringSignals|, |crossOriginTrustedScoringSignals|, |browserSignals|, + |directFromSellerSignalsForSeller|, and |auctionConfig|'s [=auction config/seller timeout=]. 1. If |auctionLevel| is "top-level-auction": 1. Set |bidDebugReportInfo|'s [=bid debug reporting info/top level seller debug loss report url=] to |debugLossReportUrl|. @@ -1964,9 +2094,12 @@ or "component-auction", a [=currency tag=] |componentAuctionExpectedCurrency|, a 1. Let |scoreAdOutput| be result of [=processing scoreAd output=] with |scoreAdResult|. 1. If |auctionLevel| is "component-auction", then set |generatedBid|'s [=generated bid/component seller=] and |bidDebugReportInfo|'s [=bid debug reporting info/component seller=] to |seller|. +1. If |auctionConfig|'s [=auction config/seller real time reporting config=] is + "`default-local-reporting`", then [=insert entries to map=] given |realTimeContributionsMap|, + |seller|, and |realTimeContributions|. 1. Return if any of the following conditions hold: * |scoreAdOutput| is failure; - * auctionLevel| is not "single-level-auction", and |scoreAdOutput| + * |auctionLevel| is not "single-level-auction", and |scoreAdOutput| ["{{ScoreAdOutput/allowComponentAuction}}"] is false; * |scoreAdOutput|["{{ScoreAdOutput/desirability}}"] ≤ 0. 1. If |auctionLevel| is "component-auction": @@ -2079,62 +2212,57 @@ The
- {{ScoringBrowserSignals/topWindowHostname}} @@ -1935,24 +2051,38 @@ or "component-auction", a [=currency tag=] |componentAuctionExpectedCurrency|, a
- {{ScoringBrowserSignals/renderURL}}
- The result of running the [=URL serializer=] on |generatedBid|'s [=generated bid/ad descriptor=]'s [=ad descriptor/url=] +
- {{ScoringBrowserSignals/renderSize}} +
- The result of running [=convert an ad size to a map=] with |generatedBid|'s + [=generated bid/ad descriptor=]'s [=ad descriptor/size=] if it is not null, {{undefined}} otherwise
- {{ScoringBrowserSignals/biddingDurationMsec}}
- |generatedBid|'s [=generated bid/bid duration=]
- {{ScoringBrowserSignals/bidCurrency}}
- The result of [=serializing a currency tag=] with |generatedBid|'s [=generated bid/bid=]'s [=bid with currency/currency=]
- {{ScoringBrowserSignals/dataVersion}} -
- |scoringDataVersion| if it is not null, {{undefined}} otherwise +
- |scoringDataVersion| if it is not null and |crossOriginTrustedScoringSignalsOrigin| is null, + unset otherwise. +
- {{ScoringBrowserSignals/crossOriginDataVersion}} +
- |scoringDataVersion| if it is not null and |crossOriginTrustedScoringSignalsOrigin| is not + null, unset otherwise.
- {{ScoringBrowserSignals/adComponents}}
- |generatedBid|'s [=generated bid/ad component descriptors=] [=converted to a string sequence=]
- {{ScoringBrowserSignals/forDebuggingOnlyInCooldownOrLockout}}
- The result of running [=is debugging only in cooldown or lockout=] with |seller|
Ad-Auction-Allowed
HTTP response header [=structured header=] whose value must be a [=structured header/boolean=].-To validate fetching response given a [=response=] |response|, null, failure, or a -[=byte sequence=] |responseBody|, and a [=string=] |mimeType|: +To validate fetching response headers given a [=response=] |response|: - 1. If |responseBody| is null or failure, return false. 1. If [=header list/getting a structured field value|getting=] [:Ad-Auction-Allowed:] and - "`item`" from |response|'s [=response/header list=] does not return a true value, return false. + "`item`" from |response|'s [=response/header list=] does not return a true value, return false. + 1. If |response|'s [=response/status=] is not an [=ok status=], return false. + 1. Return true. ++ ++To validate fetching response mime and body given a [=response=] |response|, null, +failure, or a [=byte sequence=] |responseBody|, and a [=string=] |mimeType|: + + 1. If |responseBody| is null or failure, return false. 1. Let |headerMimeType| be the result of [=header list/extracting a MIME type=] from |response|'s [=response/header list=]. 1. Return false if any of the following conditions hold: * |headerMimeType| is failure; * |mimeType| is "`text/javascript`" and |headerMimeType| is not a [=JavaScript MIME type=]; * |mimeType| is "`application/json`" and |headerMimeType| is not a [=JSON MIME type=]. - 1. Let |mimeTypeCharset| be |headerMimeType|'s [=MIME type/parameters=]["`charset`"]. - 1. Return false if any of the following conditions hold: - * |mimeTypeCharset| does not [=map/exist=], or |mimeTypeCharset| is "utf-8", and |responseBody| - is not [=UTF-8=] encoded; - * |mimeTypeCharset| is "us-ascii", and not all bytes in |responseBody| are [=ASCII bytes=]. + * |mimeType| is "`application/wasm`" and the result of [=header list/getting=] "`Content-Type`" + from |response|'s [=response/header list=] is null or not [=byte-case-insensitive=] equal to + "`application/wasm`". + + Note: This was intended to match the behavior of [=compiling a potential WebAssembly + response=], but diverges by failing to remove leading and trailing [=HTTP tab or space + bytes=]. + + 1. If |mimeType| is not "`application/wasm`": + 1. Let |mimeTypeCharset| be "utf-8". + 1. If |headerMimeType|'s [=MIME type/parameters=]["`charset`"] exists, set |mimeTypeCharset| + to |headerMimeType|'s [=MIME type/parameters=]["`charset`"]. + 1. Return true if any of the following conditions hold: + * |mimeTypeCharset| is "utf-8", and |responseBody| is [=UTF-8=] encoded; + * |mimeTypeCharset| is "us-ascii", and all bytes in |responseBody| are [=ASCII bytes=]. + 1. Return false. 1. Return true.-To fetch script given a [=URL=] |url|: - 1. Let |request| be a new [=request=] with the following properties: - : [=request/URL=] - :: |url| - : [=request/header list=] - :: «`Accept`: `text/javascript`» - : [=request/client=] - :: `null` - : [=request/mode=] - :: "`no-cors`" - : [=request/referrer=] - :: "`no-referrer`" - : [=request/credentials mode=] - :: "`omit`" - : [=request/redirect mode=] - :: "`error`" - - Issue: One of the side-effects of a `null` client for this subresource request is it neuters all - service worker interceptions, despite not having to set the service workers mode. +To validate fetching response given a [=response=] |response|, null, failure, or a +[=byte sequence=] |responseBody|, and a [=string=] |mimeType|: - Issue: Stop using "`no-cors`" mode where possible - (WICG/turtledove#667). - 1. Let |script| be null. - 1. [=Fetch=] |request| with [=fetch/useParallelQueue=] set to true, and - [=fetch/processResponseConsumeBody=] set to the following steps given a [=response=] |response| - and null, failure, or a [=byte sequence=] |responseBody|: - 1. If [=validate fetching response=] with |response|, |responseBody| and "`text/javascript`" - returns false, set |script| to failure and return. - 1. Set |script| to |responseBody|. - 1. Wait for |script| to be set. - 1. Return |script|. + 1. If the result of [=validating fetching response headers=] given |response| is false, then + return false. + 1. If the result of [=validating fetching response mime and body=] given |response|, + |responseBody|, |mimeType| is false, then return false. + 1. Return true.-To fetch WebAssembly given a [=URL=] |url|: +To fetch WebAssembly given a [=URL=] |url| and an [=origin=] |frameOrigin|: 1. Let |request| be a new [=request=] with the following properties: : [=request/URL=] @@ -2143,6 +2271,8 @@ To fetch WebAssembly given a [=URL=] |url|: :: «`Accept`: `application/wasm`» : [=request/client=] :: `null` + : [=request/origin=] + :: |frameOrigin| : [=request/mode=] :: "`no-cors`" : [=request/referrer=] @@ -2160,10 +2290,8 @@ To fetch WebAssembly given a [=URL=] |url|: 1. Let |moduleObject| be null. 1. [=Fetch=] |request| with [=fetch/processResponseConsumeBody=] set to the following steps given a [=response=] |response| and null, failure, or a [=byte sequence=] |responseBody|: - 1. Set |moduleObject| to failure and return, if any of the following conditions hold: - * |responseBody| is null or failure; - * [=header list/getting a structured field value|Getting=] [:Ad-Auction-Allowed:] and "`item`" - from |response|'s [=response/header list=] does not return a true value. + 1. If [=validate fetching response=] with |response|, |responseBody| and "`application/wasm`" + returns false, set |moduleObject| to failure and return. 1. Let |module| be the result of [=compiling a WebAssembly module=] |response|. 1. If |module| is [=error=], set |moduleObject| to failure. 1. Otherwise, set |moduleObject| to |module|. @@ -2177,17 +2305,20 @@ TheX-fledge-bidding-signals-format-version
is a [=structured header=] whose value must be an [=structured header/integer=].-To fetch trusted signals given a [=URL=] |url|, and a [=boolean=] |isBiddingSignal|: +To fetch trusted signals given a [=URL=] |url|, an [=origin=] |scriptOrigin|, +and a [=boolean=] |isBiddingSignal|: 1. Let |request| be a new [=request=] with the following properties: : [=request/URL=] :: |url| + : [=request/origin=] + :: |scriptOrigin| : [=request/header list=] :: «`Accept`: `application/json`» : [=request/client=] :: `null` : [=request/mode=] - :: "`no-cors`" + :: "`cors`" : [=request/referrer=] :: "`no-referrer`" : [=request/credentials mode=] @@ -2198,8 +2329,6 @@ To fetch trusted signals given a [=URL=] |url|, and a [=boolean=] |is Issue: One of the side-effects of a `null` client for this subresource request is it neuters all service worker interceptions, despite not having to set the service workers mode. - Issue: Stop using "`no-cors`" mode where possible - (WICG/turtledove#667). 1. Let |signals| be null. 1. Let |dataVersion| be null. 1. Let |formatVersion| be null. @@ -2221,7 +2350,7 @@ To fetch trusted signals given a [=URL=] |url|, and a [=boolean=] |is 1. Set |signals| to the result of [=parsing JSON bytes to an Infra value=] |responseBody|. 1. Wait for |signals| to be set. 1. If |signals| is a parsing exception, or if |signals| is not an [=ordered map=], return « null, - null ». + null, null ». 1. If |formatVersion| is 2: 1. If |signals|["`keys`"] does not [=map/exist=], return « null, null ». 1. Set |signals| to |signals|["`keys`"]. @@ -2249,40 +2378,6 @@ To encode trusted signals keys given an [=ordered set=] of [=strings=-A bidding signals per interest group data is a [=struct=] with the following -[=struct/items=]: - -- : updateIfOlderThanMs - :: Null or a {{double}}. If non-null, it is the [=duration=] in milliseconds after which the - [=interest group=] is eligible for an [update](#interest-group-update), if it hasn't been joined - or updated within that duration. When eligible, the update is performed either following a call - to {{Navigator/runAdAuction()}}, for each of the passed {{AuctionAdConfig/interestGroupBuyers}}, - or explicitly by the {{Navigator/updateAdInterestGroups()}} method. -
- -- -To append to a bidding signals per-interest group data map given an [=ordered map=] -|sourceMap|, an [=ordered set=] |igNames|, and an [=ordered map=] |destinationMap|: -1. [=map/For each=] |sourceKey| → |sourceValue| of |sourceMap|: - 1. If |igNames| does not [=set/contain=] |sourceKey|, [=iteration/continue=]. - 1. Let |perGroupData| be a new [=bidding signals per interest group data=]. - 1. Issue: handle priority vector - (WICG/turtledove#1144). - 1. If |sourceValue| is not an [=ordered map=], [=iteration/continue=]. - 1. If |sourceValue|["`updateIfOlderThanMs`"] is a {{double}}: - 1. Let |updateIfOlderThanMs| be |sourceValue|["`updateIfOlderThanMs`"]. - 1. If |updateIfOlderThanMs| < 600,000, set |updateIfOlderThanMs| to 600,000. - - Note: 600,000 milliseconds is 10 minutes. - 1. Set |perGroupData|'s [=bidding signals per interest group data/updateIfOlderThanMs=] to - |updateIfOlderThanMs|. - 1. If |perGroupData|'s [=bidding signals per interest group data/updateIfOlderThanMs=] is not - null, then [=map/set=] |destinationMap|[|sourceKey|] to |perGroupData|. - --To convert an ad size to a string given an [=ad size=] |adSize|. @@ -2400,13 +2495,15 @@ Note: When trusted scoring signals fetches are not batched, |renderURLs|'s [=lis-To send report given a [=URL=] |url|: +To send report given a [=URL=] |url|, and an [=origin=] |frameOrigin|: 1. Let |request| be a new [=request=] with the following properties: : [=request/URL=] :: |url| : [=request/client=] :: `null` + : [=request/origin=] + :: |frameOrigin| : [=request/mode=] :: "`no-cors`" : [=request/referrer=] @@ -2416,6 +2513,9 @@ To send report given a [=URL=] |url|: : [=request/redirect mode=] :: "`error`" + Issue: One of the side-effects of a `null` client for this subresource request is it neuters + all service worker interceptions, despite not having to set the service workers mode. + Issue: Stop using "`no-cors`" mode where possible (WICG/turtledove#667). 1. [=Fetch=] |request| with [=fetch/useParallelQueue=] set to true. @@ -2541,8 +2641,11 @@ To report result given a [=leading bid info=] |leadingBidInfo|, a given |winner|'s [=generated bid/interest group=] and |igAd| is true, then [=map/set=] |browserSignals|["{{ReportingBrowserSignals/buyerAndSellerReportingId}}"] to |igAd|'s [=interest group ad/buyer and seller reporting ID=]. - 1. Let |sellerReportingScript| be the result of [=fetching script=] with |config|'s - [=auction config/decision logic url=]. + 1. Let |sellerReportingScriptFetcher| be the result of [=creating a new script fetcher=] with + |config|'s [=auction config/decision logic url=] and |global|'s [=relevant settings object=]'s + [=environment settings object/origin=]. + 1. Let |sellerReportingScript| be the result of [=waiting for script body from a fetcher=] given + |sellerReportingScriptFetcher|. 1. Let « |sellerSignals|, |reportUrl|, |reportingBeaconMap|, ignored » be the result of [=evaluating a reporting script=] with |sellerReportingScript|, "`reportResult`", |config|'s [=auction config/config idl=]'s {{AuctionAdConfig/reportingTimeout}}, and @@ -2570,8 +2673,8 @@ To report result given a [=leading bid info=] |leadingBidInfo|, a+## {{InterestGroupBiddingAndScoringScriptRunnerGlobalScope/realTimeReporting}} ## {#real-time-reporting-header} + +*This first introductory paragraph is non-normative.* + +The goal of real-time reporting is to get auction monitoring data to the buyer and seller as quickly +as possible (e.g. < 5 mins). The primary use-case is rapid error detection i.e. detecting quickly +whether there are major problems with unexpected behavior in `generateBid()`, `scoreAd()`, or +fetching of bidding or scoring scripts or trusted signals. + +Initial implementation of this specification defines + * number of user buckets is 1024, which means buckets 0 to 1023 are supported by + {{InterestGroupBiddingAndScoringScriptRunnerGlobalScope/realTimeReporting}} API. + * number of platform buckets is 4, which are buckets for errors that are not visible in + either `scoreAd()` or `generateBid()`. + +To report win given a [=leading bid info=] |leadingBidInfo|, a [=string=] |sellerSignals|, -a {{ReportingBrowserSignals}} |browserSignals|, and a [=direct from seller signals=]-or-null -|directFromSellerSignals|: +a {{ReportingBrowserSignals}} |browserSignals|, a [=direct from seller signals=]-or-null +|directFromSellerSignals|, and an [=origin=] |frameOrigin|: 1. Let |config| be |leadingBidInfo|'s [=leading bid info/auction config=]. 1. Let |winner| be |leadingBidInfo|'s [=leading bid info/leading bid=]. @@ -2610,8 +2713,10 @@ a {{ReportingBrowserSignals}} |browserSignals|, and a [=direct from seller signa |igAd|'s [=interest group ad/buyer reporting ID=]. 1. Otherwise, [=map/Set=] |reportWinBrowserSignals|["{{ReportWinBrowserSignals/interestGroupName}}"] to |winner|'s [=generated bid/interest group=] [=interest group/name=]. - 1. Let |buyerReportingScript| be the result of [=fetching script=] with |winner|'s - [=generated bid/interest group=]'s [=interest group/bidding url=]. + 1. Let |buyerReportingScriptFetcher| be the result of [=creating a new script fetcher=] with + |winner|'s [=generated bid/interest group=]'s [=interest group/bidding url=] and |frameOrigin|. + 1. Let |buyerReportingScript| be the result of [=waiting for script body from a fetcher=] given + |buyerReportingScriptFetcher|. 1. Let |reportFunctionName| be "`reportWin`". 1. If |winner|'s [=generated bid/provided as additional bid=] is true: 1. Set |reportFunctionName| be "`reportAdditionalBidWin`". @@ -2634,6 +2739,56 @@ a {{ReportingBrowserSignals}} |browserSignals|, and a [=direct from seller signa :: |reportingMacroMap|+canLoadAdAuctionFencedFrame()
+ +*This first introductory paragraph is non-normative.* + +The process of running an ad auction through {{Window/navigator}}.{{Navigator/runAdAuction()}} is an +expensive operation. To prevent wasted cycles if the resulting ad cannot be loaded in the current +context, {{Window/navigator}}.{{Navigator/canLoadAdAuctionFencedFrame()}} is provided as a method to +determine, before the ad auction begins, whether an ad auction-created <{fencedframe}> is allowed to +be loaded in the current context. A <{fencedframe}> has restrictions on where and how it can be +loaded that do not exist with <{iframe}>s. + ++[SecureContext] +partial interface Navigator { + boolean canLoadAdAuctionFencedFrame(); +}; + + +The canLoadAdAuctionFencedFrame() method steps are: + +1. Let |global| be [=this=]'s [=relevant global object=]. + +1. If |global|'s [=Window/browsing context=]'s [=required CSP=] is not null, then return false. + + Note: CSPEE lets arbitrary data flow from an embedder into a <{fencedframe}> via the policies it + sets. Since this goes against the privacy guarantees of a Protected Audience-created + <{fencedframe}>, this is disallowed. + +1. Let |CSPList| be [=this=]'s [=relevant settings object=]'s [=environment settings object/policy + container=]'s [=policy container/CSP list=]. + +1. [=list/For each=] |policy| of |CSPList|: + + 1. [=set/For each=] |directive| of |policy|'s [=policy/directive set=]: + + 1. If |directive|'s [=directive name|name=] is either "`fenced-frame-src`", "`frame-src`", + "`child-src`", or "`default-src`", and if |directive|'s [=directive value|value=] does not + [=set/contain=] any of "`https:`", "`https://*:*`", or "`*`", then return false. + +1. Let |sandboxFlags| be |global|'s [=associated Document=]'s [=Document/active sandboxing flag + set=]. + +1. If the [=set/intersection=] of |sandboxFlags| and TBD [=set/is not empty=], then return false. + + Issue: TODO: Get the mandatory unsandboxed [=sandboxing flag set=] once it exists in the fenced + frame spec. This will then determine the intersection between that and the |sandboxFlags|. + (WICG/turtledove#1206) + +1. Return true. + # Reporting # {#reporting} ## {{InterestGroupBiddingAndScoringScriptRunnerGlobalScope/forDebuggingOnly}} ## {#for-debugging-only-header} @@ -2793,6 +2948,220 @@ and [=map/values=] are [=moments=] at which the cool down for the origin key exp 1. Return |canSendAfterSampled|.++ +Each real time report is a [[RFC8949|CBOR]] message using the following data structure:
++ { + "version": 1, + "histogram": { + "buckets": [5, 32, 0, 1, ..., 129], // 128 elements. + "length": 1024 // 1024 buckets before bit packing. + }, + "platformHistogram": { + "buckets": [192], + "length": 4 // 4 buckets before bit packing. + } + } +++ To sample real time contributions given a [=list=] of + [=real time reporting contributions=] |contributions|, perform the following steps. They return an + integer or null: + + 1. If |contributions| [=list/is empty=], then return null. + 1. Let |priorityWeightSum| be 0. + 1. [=list/For each=] |contribution| of |contributions|: + 1. Increment |priorityWeightSum| by |contribution|'s + [=real time reporting contribution/priority weight=]. + 1. Let |sampleRand| be a random {{double}}, 0 ≤ sampleRand < 1. + 1. Set |sampleRand| to |sampleRand| multiplied by |priorityWeightSum|. + 1. Set |priorityWeightSum| to 0. + 1. Let |selectedBucket| be -1. + 1. [=list/For each=] |contribution| of |contributions|: + 1. Increment |priorityWeightSum| by |contribution|'s + [=real time reporting contribution/priority weight=]. + 1. If |priorityWeightSum| ≥ |sampleRand|, then set |selectedBucket| to |contribution|'s + [=real time reporting contribution/bucket=], and [=iteration/break=]; + 1. [=Assert=] |selectedBucket| is not -1. + 1. Return |selectedBucket|. ++ ++ To bit pack a [=list=] of [=booleans=] |input|: + + 1. Let |inputSize| be |input|'s [=list/size=]. + 1. Let |packed| be a new [=list=] of [=bytes=]. + 1. Let |currentByte| be 0. + 1. Let |numBits| be 0. + 1. [=list/For each=] |i| in [=the range=] from 0 to |inputSize|, exclusive: + 1. Set |currentByte| to |currentByte| * 2 + |input|[|i|]. + 1. Increment |numBits| by 1. + 1. If |numBits| is 8, then: + 1. [=list/Append=] |currentByte| to |packed|. + 1. Set |currentByte| to 0 and |numBits| to 0. + 1. Otherwise if |i| is |inputSize| - 1, then: + 1. [=iteration/While=] |numBits| < 8: + 1. Set |currentByte| to |currentByte| * 2. + 1. [=list/Append=] |currentByte| to |packed|. + 1. Increment |numBits| by 1. + 1. [=Assert=] that |packed|'s [=list/size=] is (|inputSize| + 7) / 8.0. + 1. Return |packed|. ++ ++ To send a real time report given a [=URL=] |url|, a [=list=] of [=booleans=] + |histogram|, and an [=origin=] |frameOrigin|: + + 1. Let |totalBuckets| be the sum of [=number of user buckets=] and [=number of platform buckets=]. + 1. [=Assert=] |histogram|'s [=list/size=] is |totalBuckets|. + 1. Let |userHistogram| and |platformHistogram| be new [=lists=] of [=booleans=]. + 1. [=list/For each=] |i| in [=the range=] 0 to |totalBuckets|, exclusive: + 1. If |i| < |userHistogram|, then [=list/append=] |histogram|[|i|] to |userHistogram|. + 1. Otherwise, [=list/append=] |histogram|[|i|] to |platformHistogram|. + + 1. Let |body| be a new [=ordered map=] of the following [=map/entries=]: + : "version" + :: 1 + : "histogram" + :: a new [=ordered map=] of the following [=map/entries=]: + : "buckets" + :: the result of [=bit packing=] with |userHistogram| + : "length" + :: [=number of user buckets=] + : "platformHistogram" + :: a new [=ordered map=] of the following [=map/entries=]: + : "buckets" + :: the result of [=bit packing=] with |platformHistogram| + : "length" + :: [=number of platform buckets=] + 1. Let |request| be a new [=request=] with the following properties: + : [=request/URL=] + :: |url| + : [=request/header list=] + :: «`Content-Type`: `application/cbor`» + : [=request/method=] + :: `POST` + : [=request/body=] + :: the [=byte sequence=] resulting from [[RFC8949#name-specification-of-the-cbor-e|CBOR encoding]] + |body| + : [=request/client=] + :: `null` + : [=request/origin=] + :: |frameOrigin| + : [=request/mode=] + :: "`no-cors`" + : [=request/referrer=] + :: "`no-referrer`" + : [=request/credentials mode=] + :: "`omit`" + : [=request/redirect mode=] + :: "`error`" + + Issue: One of the side-effects of a `null` client for this subresource request is it neuters + all service worker interceptions, despite not having to set the service workers mode. + + Issue: Stop using "`no-cors`" mode where possible + (WICG/turtledove#667). + 1. [=Fetch=] |request| with [=fetch/useParallelQueue=] set to true. ++ ++ To send real time reports given a [=real time reporting contributions map=] + |contributionsMap| and an [=origin=] |frameOrigin|: + + 1. [=map/For each=] |origin| → |contributions| of |contributionsMap|: + 1. Let |maybeBucket| be the result of [=sampling real time contributions=] with |contributions|. + 1. Let |histogram| be the result of [=applying RAPPOR noise=] with |maybeBucket|. + 1. Let |reportUrl| be a new [=URL=] with the following [=struct/items=]: + : [=url/scheme=] + :: |origin|'s [=origin/scheme=] + : [=url/host=] + :: |origin|'s [=origin/host=] + : [=url/port=] + :: |origin|'s [=origin/port=] + : [=url/path=] + :: « ".well-known", "interest-group", "real-time-report" » + 1. [=Send a real time report=] with |reportUrl|, |histogram| and |frameOrigin|. + + Issue: TODO: Spec rate limiting. + (WICG/turtledove#1215) ++ + +### Platform contribution ### {#platform-contribution-header} + +*This first introductory paragraph is non-normative.* + +There are some errors not visible in either `scoreAd()` or `generateBid()`, like failures to fetch +the bidding or scoring script, trusted bidding or scoring signals. Certain buckets are used for +these errors. + +The initial implementation supports four platform contribution buckets starting from +[=number of user buckets=]: +* bidding script failure bucket: [=number of user buckets=] +* scoring script failure bucket: [=number of user buckets=] plus 1 +* trusted bidding signals failure bucket: [=number of user buckets=] plus 2 +* trusted scoring signals failure bucket: [=number of user buckets=] plus 3 + +Platform contributions have a hardcoded platform contribution priority weight. The +initial implementation uses a value of 1. + ++ To add a platform contribution given a {{long}} |bucket|, a + [=real time reporting contributions map=] |realTimeContributionsMap|, and an [=origin=] |origin|: + + 1. [=Assert=] |bucket| is one of [=platform contribution buckets=]. + 1. Let |contribution| be a new [=real time reporting contribution=] with the following [=struct/items=]: + : [=real time reporting contribution/bucket=] + :: |bucket| + : [=real time reporting contribution/priority weight=] + :: [=platform contribution priority weight=] + 1. [=Insert entries to map=] given |realTimeContributionsMap|, |origin|, and « |contribution| » . ++ +### Apply noise (RAPPOR) ### {#rappor-header} + +*This first introductory paragraph is non-normative.* + +Basic +RAPPOR noises each coordinate of the bit vector independently, and it is parameterized by +epsilon, a measure of privacy loss. The initial implementation uses an [=epsilon=] value of +1, yielding a flipping probability of ~0.378. + ++ To apply RAPPOR noise given an integer-or-null |maybeBucket|: + + 1. Let |totalBuckets| be the sum of [=number of user buckets=] and [=number of platform buckets=]. + 1. Let |histogram| be a new [=list=] of [=booleans=], whose [=list/size=] is |totalBuckets|. + 1. If |maybeBucket| is not null: + 1. [=Assert=] 0 ≤ |maybeBucket| < |totalBuckets|. + 1. Set |histogram|[|maybeBucket|] to true. + 1. Let |f| be+ # Additional Bids and Negative Targeting # {#additional-bids-and-negative-targeting} ## createAuctionNonce() ## {#create-auction-nonce} @@ -3482,12 +3851,14 @@ of the following global objects:2.0/(1+e[=epsilon=]/2.0)
. + 1. [=list/For each=] |i| in [=the range=] from 0 to |totalBuckets|, exclusive: + 1. Let |rand| be a random {{double}}, 0 ≤ |rand| < 1 + 1. If |rand| < |f| / 2.0, then: + 1. Let |flipped| be false if |histogram|[|i|] is true, otherwise true. + 1. Set |histogram|[|i|] to |flipped|. + 1. Return |histogram|. +To evaluate a bidding script given a [=string=] |script|, an {{unsigned short}} - |multiBidLimit|, an [=interest group=] |ig|, a [=currency tag=] |expectedCurrency|, a {{GenerateBidInterestGroup}} |igGenerateBid|, a [=string=]-or-null - |auctionSignals|, a [=string=]-or-null |perBuyerSignals|, an [=ordered map=] |trustedBiddingSignals|, + |auctionSignals|, a [=string=]-or-null |perBuyerSignals|, an [=ordered map=]-or-null + |sameOriginTrustedBiddingSignals|, an [=ordered map=]-or-null |crossOriginTrustedBiddingSignals|, a {{BiddingBrowserSignals}} |browserSignals|, a {{DirectFromSellerSignalsForBuyer}} - |directFromSellerSignalsForBuyer| and an integer millisecond [=duration=] |timeout|: + |directFromSellerSignalsForBuyer|, and an integer millisecond [=duration=] |timeout|, perform the + following steps. They return a [=tuple=] ([=list=] of [=generated bids=], + [=bid debug reporting info=], [=list=] of [=real time reporting contributions=]). 1. Let |realm| be the result of [=creating a new script runner realm=] given {{InterestGroupBiddingScriptRunnerGlobalScope}}. @@ -3512,14 +3883,18 @@ of the following global objects: |auctionSignals| if |auctionSignals| is not null, otherwise {{undefined}}. 1. Let |perBuyerSignalsJS| be the result of [=parsing a JSON string to a JavaScript value=] given |perBuyerSignals| if |perBuyerSignals| is not null, otherwise {{undefined}}. - 1. Let |trustedBiddingSignalsJS| be |trustedBiddingSignals| [=converted to ECMAScript values=]. + 1. Let |sameOriginTrustedBiddingSignalsJS| be |sameOriginTrustedBiddingSignals| + [=converted to ECMAScript values=]. 1. Let |browserSignalsJS| be |browserSignals| [=converted to ECMAScript values=]. - 1. Let |directFromSellerSignalsJs| be |directFromSellerSignalsForBuyer| + 1. Let |directFromSellerSignalsJS| be |directFromSellerSignalsForBuyer| [=converted to ECMAScript values=]. + 1. Let |crossOriginTrustedBiddingSignalsJS| be |crossOriginTrustedBiddingSignals| + [=converted to ECMAScript values=]. 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|. + « |igJS|, |auctionSignalsJS|, |perBuyerSignalsJS|, |sameOriginTrustedBiddingSignalsJS|, + |browserSignalsJS|, |directFromSellerSignalsJS|, |crossOriginTrustedBiddingSignalsJS| », + and |timeout|. 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: @@ -3548,7 +3923,7 @@ of the following global objects: to a new [=list=] of [=generated bids=]. 1. Let |bidDebugReportInfo| be a new [=bid debug reporting info=]. 1. Set |bidDebugReportInfo|'s [=bid debug reporting info/interest group owner=] to - |ig|'s [=interest group/owner=] + |ig|'s [=interest group/owner=]. 1. Let |debugLossReportUrl| be |global|'s [=InterestGroupBiddingAndScoringScriptRunnerGlobalScope/debug loss report url=] if it's not failure, null otherwise. @@ -3560,34 +3935,53 @@ of the following global objects: 1. Set |bidDebugReportInfo|'s [=bid debug reporting info/bidder debug win report url=] to |debugWinReportUrl|. 1. [=list/For each=] |generatedBid| in |generatedBids|: 1. Set |generatedBid|'s [=generated bid/bid duration=] to |duration|, - [=generated bid/interest group=] to |ig|, - 1. Return « |generatedBids|, |bidDebugReportInfo| ». + [=generated bid/interest group=] to |ig|. + 1. Let |realTimeContributions| be a new [=list=] of [=real time reporting contributions=]. + 1. [=list/For each=] |contribution| of |global|'s + [=InterestGroupBiddingAndScoringScriptRunnerGlobalScope/real time reporting contributions=]: + 1. If |contribution|'s [=real time reporting contribution/latency threshold=] is not null, and + ≥ |duration|, then [=iteration/continue=]. + 1. [=list/Append=] |contribution| to |realTimeContributions|. + 1. Return a [=tuple=] (|generatedBids|, |bidDebugReportInfo|, |realTimeContributions|).To evaluate a scoring script given a [=string=] |script|, a [=string=] |adMetadata|, - a {{double}} |bidValue|, an {{AuctionAdConfig}} |auctionConfigIDL|, an [=ordered map=] - |trustedScoringSignals|, a {{ScoringBrowserSignals}} |browserSignals|, a - {{DirectFromSellerSignalsForSeller}} |directFromSellerSignalsForSeller|, and an integer - millisecond [=duration=] |timeout|: + a {{double}} |bidValue|, an {{AuctionAdConfig}} |auctionConfigIDL|, an [=ordered map=]-or-null + |sameOriginTrustedScoringSignals|, an [=ordered map=]-or-null |crossOriginTrustedScoringSignals|, + {{ScoringBrowserSignals}} |browserSignals|, a {{DirectFromSellerSignalsForSeller}} + |directFromSellerSignalsForSeller|, and an integer millisecond [=duration=] |timeout|: 1. Let |realm| be the result of [=creating a new script runner realm=] given {{InterestGroupScoringScriptRunnerGlobalScope}}. 1. Let |browserSignalsJS| be |browserSignals| [=converted to ECMAScript values=]. 1. Let |auctionConfigJS| be |auctionConfigIDL| [=converted to ECMAScript values=]. - 1. Let |trustedScoringSignalsJS| be |trustedScoringSignals| [=converted to ECMAScript values=]. + 1. Let |sameOriginTrustedScoringSignalsJS| be |sameOriginTrustedScoringSignals| + [=converted to ECMAScript values=]. + 1. Let |crossOriginTrustedScoringSignalsJS| be |crossOriginTrustedScoringSignals| + [=converted to ECMAScript values=]. 1. Let |directFromSellerSignalsJs| be |directFromSellerSignalsForSeller| [=converted to ECMAScript values=]. + 1. Let |startTime| be |settings|'s [=environment settings object/current monotonic time=]. 1. Let |scoreAdResult| be the result of [=evaluating a script=] with |realm|, |script|, "`scoreAd`", - «|adMetadata|, |bidValue|, |auctionConfigJS|, |trustedScoringSignalsJS|, |browserSignalsJS|, - |directFromSellerSignalsJs|», and |timeout|. + «|adMetadata|, |bidValue|, |auctionConfigJS|, |sameOriginTrustedScoringSignalsJS|, + |browserSignalsJS|, |directFromSellerSignalsJs|, |crossOriginTrustedScoringSignalsJS|», + and |timeout|. + 1. Let |duration| be |settings|'s [=environment settings object/current monotonic time=] minus + |startTime| in milliseconds. 1. Let |debugWinReportUrl| be |global|'s [=InterestGroupBiddingAndScoringScriptRunnerGlobalScope/debug win report url=] if it's not failure, null otherwise. 1. Let |debugLossReportUrl| be |global|'s [=InterestGroupBiddingAndScoringScriptRunnerGlobalScope/debug loss report url=] if it's not failure, null otherwise. - 1. Return « |scoreAdResult|, |debugWinReportUrl|, |debugLossReportUrl| ». + 1. Let |realTimeContributions| be a new [=list=] of [=real time reporting contributions=]. + 1. [=list/For each=] |contribution| of |global|'s + [=InterestGroupBiddingAndScoringScriptRunnerGlobalScope/real time reporting contributions=]: + 1. If |contribution|'s [=real time reporting contribution/latency threshold=] is not null, and + ≥ |duration|, then [=iteration/continue=]. + 1. [=list/Append=] |contribution| to |realTimeContributions|. + 1. Return « |scoreAdResult|, |debugWinReportUrl|, |debugLossReportUrl|, |realTimeContributions| ».@@ -3693,19 +4087,31 @@ interface ForDebuggingOnly { undefined reportAdAuctionLoss(USVString url); }; +[Exposed=InterestGroupBiddingAndScoringScriptRunnerGlobalScope] +interface RealTimeReporting { + undefined contributeToHistogram(RealTimeContribution contribution); +}; + +dictionary RealTimeContribution { + required long bucket; + required double priorityWeight; + long latencyThreshold; +}; + [Exposed=InterestGroupBiddingAndScoringScriptRunnerGlobalScope, Global=InterestGroupBiddingAndScoringScriptRunnerGlobalScope] interface InterestGroupBiddingAndScoringScriptRunnerGlobalScope : InterestGroupScriptRunnerGlobalScope { - readonly attribute ForDebuggingOnly forDebuggingOnly; + readonly attribute RealTimeReporting realTimeReporting; }; -Each {{InterestGroupBiddingAndScoringScriptRunnerGlobalScope}} has an associated -forDebuggingOnly, which is an -{{ForDebuggingOnly}} instance created alongside the -{{InterestGroupBiddingAndScoringScriptRunnerGlobalScope}}. +Each {{InterestGroupBiddingAndScoringScriptRunnerGlobalScope}} has an associated {{ForDebuggingOnly}} +instance forDebuggingOnly, and +an associated {{RealTimeReporting}} instance +realTimeReporting, which are +created alongside the {{InterestGroupBiddingAndScoringScriptRunnerGlobalScope}}.+@@ -3758,6 +4164,45 @@ The reportAdAuctionLoss(|url|) method s [=InterestGroupBiddingAndScoringScriptRunnerGlobalScope/debug loss report url=] to |parsedUrl|.+ ++The realTimeReporting +getter steps are: + + 1. Return [=this=]'s [=relevant global object=]'s + [=InterestGroupBiddingAndScoringScriptRunnerGlobalScope/realTimeReporting=]. ++ +Each {{InterestGroupBiddingAndScoringScriptRunnerGlobalScope}} has a ++ : real time reporting contributions + :: A [=list=] of [=real time reporting contributions=]. +
+ ++The contributeToHistogram({{RealTimeContribution}} |contribution|) +method steps are: + + 1. Let |global| be [=this=]'s [=relevant global object=]. + 1. If |contribution|["{{RealTimeContribution/bucket}}"] ≥ [=number of user buckets=] or is + negative, then return; + + Note: For forward compatibility with new values, don't [=exception/throw=]. + + 1. If |contribution|["{{RealTimeContribution/priorityWeight}}"] ≤ 0, then [=exception/throw=] a + {{TypeError}}. + 1. Let |contributionEntry| be a new [=real time reporting contribution=] with the following + [=struct/items=]: + : [=real time reporting contribution/bucket=] + :: |contribution|["{{RealTimeContribution/bucket}}"] + : [=real time reporting contribution/priority weight=] + :: |contribution|["{{RealTimeContribution/priorityWeight}}"] + : [=real time reporting contribution/latency threshold=] + :: |contribution|["{{RealTimeContribution/latencyThreshold}}"] if it [=map/exists=], null otherwise + 1. [=list/Append=] |contributionEntry| to |global|'s + [=InterestGroupBiddingAndScoringScriptRunnerGlobalScope/real time reporting contributions=]. ++ ### InterestGroupBiddingScriptRunnerGlobalScope ### {#bidding-global-scope}@@ -4158,6 +4603,8 @@ navigating to another page. Some implementations, such as Chromium, have chosen :: «`Accept`: `application/json`» : [=request/client=] :: `null` + : [=request/origin=] + :: |owner| : [=request/mode=] :: "`no-cors`" : [=request/referrer=] @@ -4254,9 +4701,8 @@ navigating to another page. Some implementations, such as Chromium, have chosen"`biddingLogicURL`" "`biddingWasmHelperURL`" "`updateURL`" - "`trustedBiddingSignalsURL`" - 1. For each |groupMember| and |interestGroupField| in the following table + 1. For each |groupMember| and |interestGroupField|, in the following table - 1. Let |parsedURL| be the result of running the [=URL parser=] on |value|. 1. If |key| is not |groupMember|, [=iteration/continue=]. - 1. Jump to the step labeled Abort update - if any of the following conditions hold: - * |parsedURL| is failure; - * |parsedURL|'s [=url/origin=] is not [=same origin=] with |ig|'s - [=interest group/owner=]; - * |parsedURL| [=includes credentials=]; - * |parsedURL| [=url/fragment=] is not null. - 1. Set |ig|'s |interestGroupField| to |parsedURL|. + 1. If |value| is a [=string=]: + 1. Let |parsedURL| be the result of [=parse and verify a bidding code or update URL=] + on |value| and |ig|'s [=interest group/owner=]. + 1. If |parsedURL| is failure, jump to the step labeled + Abort update + 1. Set |ig|'s |interestGroupField| to |parsedURL|. + +
Group member Interest group field @@ -4271,21 +4717,22 @@ navigating to another page. Some implementations, such as Chromium, have chosen -"`updateURL`" [=interest group/update url=] - "`trustedBiddingSignalsURL`" -[=interest group/trusted bidding signals url=] -"`trustedBiddingSignalsURL`" ++ 1. If |value| is a [=string=]: + 1. Let |parsedURL| be the result of [=parse and verify a trusted signals URL=] on |value|. + 1. If |parsedURL| is failure, jump to the step labeled + Abort update + 1. Set |ig|'s [=interest group/trusted bidding signals url=] to |parsedURL|. "`trustedBiddingSignalsKeys`" @@ -4374,7 +4821,7 @@ navigating to another page. Some implementations, such as Chromium, have chosen following conditions hold: * |ig|'s [=interest group/ads=] is not null, and |ig|'s [=interest group/additional bid key=] is not null; - * |ig|'s [=interest group/estimated size=] is greater than 50 KB. + * |ig|'s [=interest group/estimated size=] is greater than 1048576 bytes. 1. Set |ig|'s [=interest group/next update after=] to the [=current wall time=] plus 24 hours. 1. Set |ig|'s [=interest group/last updated=] to the [=current wall time=]. 1. [=list/Replace=] the [=interest group=] that has |ig|'s [=interest group/owner=] and @@ -4408,7 +4855,9 @@ To process updateIfOlderThanMs given an [=origin=] |buyer|, and an [= # Feature Detection # {#feature-detection} -The {{ProtectedAudience/queryFeatureSupport()}} method permits checking what functionality is available in the current implementation, in order to help deploy new features. The return values specified in this specification are for an implementation that fully implements it. +The {{ProtectedAudience/queryFeatureSupport()}} method permits checking what functionality is +available in the current implementation, in order to help deploy new features. The return values +specified in this specification are for an implementation that fully implements it. [SecureContext] @@ -4425,8 +4874,21 @@ interface ProtectedAudience { The queryFeatureSupport(feature) method steps are: -1. If feature is "adComponentsLimit", return 40. -2. Return `undefined`. +1. Let |featuresTable| be an [=ordered map=] whose keys are {{DOMString}}s and whose values are + {{boolean}}s or {{long}}s, with the following entries: + : "adComponentsLimit" + :: 40 + : "deprecatedRenderURLReplacements" + :: true + : "permitCrossOriginTrustedSignals" + :: true + : "realTimeReporting" + :: true + : "reportingTimeout" + :: true +1. If |feature| is "*", then return |featuresTable|. +1. If |featuresTable|[|feature|] [=map/exists=], then return |featuresTable|[|feature|]. +1. Return `undefined`.@@ -4469,7 +4931,8 @@ The estimated size of an [=interest group=] |ig| Note: Each of [=interest group/trusted bidding signals slot size mode=]'s value represents a {{long}} integer, so 4 bytes. -1. [=list/For each=] |key| of |ig|'s [=interest group/trusted bidding signals keys=]: +1. If |ig|'s [=interest group/trusted bidding signals keys=] is not null, + [=list/for each=] |key| of it: 1. The [=string/length=] of |key|. 1. If |ig|'s [=interest group/max trusted bidding signals url length=] is not null: 1. 4, which is the size of |ig|'s [=interest group/max trusted bidding signals url length=]. @@ -4508,6 +4971,33 @@ The estimated size of an [=interest group=] |ig| 1. Return the [=URL serializer|serialization=] of |url|.+ To parse and verify a bidding code or update URL given a [=string=] |input| and an + [=origin=] |igOwner|: + + 1. Let |parsedUrl| be the result of running the [=URL parser=] on |input|. + 1. Return failure if any of the following conditions hold: + * |parsedUrl| is failure; + * |parsedUrl|'s [=url/origin=] is not [=same origin=] with |igOwner|; + * |parsedUrl| [=includes credentials=]; + * |parsedUrl|'s [=url/fragment=] is not null. + 1. [=Assert=]: |parsedUrl|'s [=url/scheme=] is "`https`". + 1. Return |parsedUrl|. ++ ++ To parse and verify a trusted signals URL given a [=string=] |input|: + + 1. Let |parsedUrl| be the result of running the [=URL parser=] on |input|. + 1. Return failure if any of the following conditions hold: + * |parsedUrl| is failure; + * |parsedUrl|'s [=url/scheme=] is not "`https`"; + * |parsedUrl| [=includes credentials=]; + * |parsedUrl|'s [=url/fragment=] is not null; + * |parsedUrl|'s [=url/query=] is not null. + 1. Return |parsedUrl|. ++To round a value given a {{double}} |value|: @@ -4551,6 +5041,14 @@ Issue: This would ideally be replaced by a more descriptive algorithm in Infra. 1. Return [=ASCII encoded=] |uuidStr|.++ To insert entries to map given an [=ordered map=] |map|, a |key| which is in same type + as |map|'s [=map/key=], and a [=list=] |entries| which is in same type as |map|'s [=map/value=]: + + 1. If |map|[|key|] [=map/exists=], then [=list/extend=] |map|[|key|] with |entries|. + 1. Otherwise, [=map/set=] |map|[|key|] to |entries|. ++ # Permissions Policy Integration # {#permissions-policy-integration} @@ -4893,10 +5391,12 @@ dictionary BiddingBrowserSignals { required long adComponentsLimit; required unsigned short multiBidLimit; + recordrequestedSize; USVString topLevelSeller; sequence prevWinsMs; object wasmHelper; unsigned long dataVersion; + unsigned long crossOriginDataVersion; boolean forDebuggingOnlyInCooldownOrLockout = false; }; @@ -4907,12 +5407,28 @@ dictionary ScoringBrowserSignals { required unsigned long biddingDurationMsec; required DOMString bidCurrency; + record renderSize; unsigned long dataVersion; + unsigned long crossOriginDataVersion; sequence adComponents; boolean forDebuggingOnlyInCooldownOrLockout = false; }; + + To convert an ad size to a map given an [=ad size=] |adSize|: + + 1. Let |dict| be a new empty [=map=]. + 1. Let |jsWidth| be |adSize|'s [=ad size/width=], [=converted to an ECMAScript value=]. + 1. [=map/Set=] |dict|["width"] to the result of [=string/concatenating=] « [$ToString$](|jsWidth|), + |adSize|'s [=ad size/width units=] ». + 1. Let |jsHeight| be |adSize|'s [=ad size/height=], [=converted to an ECMAScript value=]. + 1. [=map/Set=] |dict|["height"] to the result of [=string/concatenating=] « [$ToString$](|jsHeight|), + |adSize|'s [=ad size/height units=] ». + 1. Return |dict|. + ++ Note: {{ScoringBrowserSignals}}'s {{ScoringBrowserSignals/adComponents}} is {{undefined}} when [=generated bid/ad component descriptors=] is null or [=list/is empty|an empty list=]. It cannot be an [=list/is empty|empty list=]. @@ -5120,12 +5636,8 @@ An interest group is a [=struct=] with the following [=struct/items=] : 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=]. + :: Null or a [=list=] of [=strings=]. See [=building trusted bidding signals url=]. : trusted bidding signals slot size mode :: "`none`", "`slot-size`" or "`all-slots-requested-sizes`". Initially "`none`". Each value reprensents a {{long}} integer. See [=calculate the ad slot size query param=]. @@ -5266,10 +5778,6 @@ An auction config is a [=struct=] with the following [=struct/items=] 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=]. -
: max trusted scoring signals url length :: A {{long}} integer, initially 0. Indicates the maximum trusted scoring signals fetch url length for the auction config. 0 means no limit. @@ -5422,6 +5930,16 @@ An auction config is a [=struct=] with the following [=struct/items=] :: 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 real time reporting config + :: Null, or a [=string=], initially null. Seller's real time reporting type. Currently the only + supported type is "default-local-reporting" indicating local differential privacy. If not null, + the seller opted in to receive real time reports. + : per buyer real time reporting config + :: An [=ordered map=], whose [=map/keys=] are [=origins=], and whose [=map/values=] are [=strings=]. + Each buyer's real time reporting type. Currently the only supported type is + "default-local-reporting" indicating local differential privacy. All buyers in the map opted in + to receive real time reports. +@@ -5437,7 +5955,8 @@ To wait until configuration input promises resolve given an [=auction [=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=], [=auction config/deprecated render url replacements=], or + [=auction config/per buyer timeouts=], [=auction config/per buyer cumulative timeouts=], + [=auction config/deprecated render url replacements=], or [=auction config/direct from seller signals header ad slot=] is failure, return failure. 1. Return.@@ -5493,13 +6012,278 @@ To look up per-buyer multi-bid limit given an [=auction config=] |aucBid 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 -[=per signals url bid generators=]. +or null representing [=interest group/trusted bidding signals urls=], and whose [=map/values=] are +[=per signals url bid generators=]. The key of null is used for [=interest groups=] that do not +specify a [=interest group/trusted bidding signals url=]. 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=]. +Script fetcher
+ +A script fetcher helps manage asynchronous fetching of scripts and handling of their +headers. It's a [=struct=] with the following [=struct/items=]: + ++ : script body + :: A [=byte sequence=], null, or failure. Initially null. The body of the script. + : origins authorized for cross origin trusted signals + :: A [=list=] of [=origins=] or null. Initially null. Parsed value of + [:Ad-Auction-Allow-Trusted-Scoring-Signals-From:]. +
+ ++To create a new script fetcher given a [=URL=] |url| and an [=origin=] |frameOrigin|: + + 1. Let |fetcher| be a new [=script fetcher=]. + 1. Let |queue| be the result of [=starting a new parallel queue=]. + 1. [=parallel queue/enqueue steps|Enqueue the following steps=] to |queue|: + 1. [=Fetch script=] given |url|, |frameOrigin| and |fetcher|. + 1. Return |fetcher|. ++ ++To wait for script body from a fetcher given a [=script fetcher=] |fetcher|: + + 1. Wait until |fetcher|'s [=script fetcher/script body=] is not null. + 1. Return |fetcher|'s [=script fetcher/script body=]. ++ ++To wait for cross origin trusted scoring signals authorization from a fetcher given a +[=script fetcher=] |fetcher|: + + 1. Wait until |fetcher|'s [=script fetcher/origins authorized for cross origin trusted signals=]. + is not null. + 1. Return |fetcher|'s [=script fetcher/origins authorized for cross origin trusted signals=]. ++ +TheAd-Auction-Allow-Trusted-Scoring-Signals-From
HTTP response +header is a [=structured header=] whose value must be a [=structured header/list=] of [=structured +header/strings=]. + ++To parse allowed trusted scoring signals origins given a [=header list=] |headerList|: + + 1. Let |parsedHeader| be the result of [=header list/getting a structured field value=] + given [:Ad-Auction-Allow-Trusted-Scoring-Signals-From:] and "`list`" from |headerList|. + 1. If |parsedHeader| is null, return an empty [=list=]. + 1. Let |result| be a new [=list=] of [=origins=]. + 1. [=list/For each=] |entry| in |parsedHeader|: + 1. If |entry| is not a [=string=], return an empty [=list=]. + 1. Let |parsedEntry| be the result of [=parsing an https origin=] on |entry|. + 1. If |parsedEntry| is failure, return an empty [=list=]. + 1. [=list/Append=] |parsedEntry| to |result|. + 1. Return |result|. ++ ++To fetch script given a [=URL=] |url|, an [=origin=] |frameOrigin|, and a +[=script fetcher=] |fetcher|: + 1. Let |request| be a new [=request=] with the following properties: + : [=request/URL=] + :: |url| + : [=request/header list=] + :: «`Accept`: `text/javascript`» + : [=request/client=] + :: `null` + : [=request/origin=] + :: |frameOrigin| + : [=request/mode=] + :: "`no-cors`" + : [=request/referrer=] + :: "`no-referrer`" + : [=request/credentials mode=] + :: "`omit`" + : [=request/redirect mode=] + :: "`error`" + + Issue: One of the side-effects of a `null` client for this subresource request is it neuters all + service worker interceptions, despite not having to set the service workers mode. + + Issue: Stop using "`no-cors`" mode where possible + (WICG/turtledove#667). + 1. Let |fetchController| be the result of [=fetching=] |request| with [=fetch/useParallelQueue=] + set to true, and [=fetch/processResponse=] set to the following steps given + a [=response=] |response|: + 1. If the result of [=validating fetching response headers=] given |response| is false: + 1. [=fetch controller/Abort=] |fetchController|. + 1. Set |fetcher|'s [=script fetcher/origins authorized for cross origin trusted signals=] to + an empty [=list=] of [=origins=]. + 1. Set |fetcher|'s [=script fetcher/script body=] to failure. + 1. Set |fetcher|'s [=script fetcher/origins authorized for cross origin trusted signals=] to the + result of [=parsing allowed trusted scoring signals origins=] given |response|'s [=response/ + header list=]. + 1. Let |bodyStream| be |response|’s [=response/body=]’s [=body/stream=]. + 1. Let |bodyReader| be result of [=ReadableStream/getting a reader=] from |bodyStream|. + 1. Let |successSteps| be a set of steps that take a [=byte sequence=] |responseBody|, and + perform the following: + 1. If [=validate fetching response mime and body=] with |response|, |responseBody| and + "`text/javascript`" returns false, set |fetcher|'s [=script fetcher/script body=] to + failure. + 1. Otherwise, set set |fetcher|'s [=script fetcher/script body=] to |responseBody|. + 1. Let |failureSteps| be a set of steps that take an [=exception=] e, and + perform the following: + 1. Set set |fetcher|'s [=script fetcher/script body=] to failure. + 1. [=ReadableStreamDefaultReader/Read all bytes=] from |bodyReader|, given |successSteps| + and |failureSteps|. ++ +Trusted bidding signals batcher
+ +A trusted bidding signals batcher helps manage merging multiple trusted bidding signals +into smaller number of fetches. It's a [=struct=] with the following [=struct/items=]: + ++ : all trusted bidding signals + :: An [=ordered map=] whose [=map/keys=] are [=strings=], and [=map/values=] are [=strings=]. + Contains all trusted bidding signals collected thus far. + : all per interest group data + :: An [=ordered map=] whose [=map/keys=] are [=interest group/name=] [=strings=] and whose + [=map/values=] are [=bidding signals per interest group data=]. + : no signals flags + :: An [=ordered map=] whose [=map/keys=] are [=interest group/name=] [=strings=] and whose + [=map/values=] are enums "no-fetch" or "fetch-failed". This is set to "not-fetched" if given + interest group did not request any trusted bidding signals keys, or "fetch-failed" if its + trusted signals fetch failed. + : data versions + :: An [=ordered map=] who [=map/keys=] are [=interest group/name=] [=strings=] and [=map/values=] + are {{unsigned long}} or null. This contains data version returned by a fetch that provided the + values for a given interest group name. + : keys + :: An [=ordered set=] of [=strings=]. Describes the keys collected to be fetched in the current + batch thus far. + : ig names + :: An [=ordered set=] of [=strings=]. Describes the interest group names to be fetched in the + current batch thus far. + : length limit + :: A {{long}}, initially 2147483647 (the maximum value that it can hold). Describes the URL length + limit the current batch is limited to, the smallest of the limits of the interest groups included. +
+ +A bidding signals per interest group data is a [=struct=] with the following +[=struct/items=]: + ++ : updateIfOlderThanMs + :: Null or a {{double}}. If non-null, it is the [=duration=] in milliseconds after which the + [=interest group=] is eligible for an [update](#interest-group-update), if it hasn't been joined + or updated within that duration. When eligible, the update is performed either following a call + to {{Navigator/runAdAuction()}}, for each of the passed {{AuctionAdConfig/interestGroupBuyers}}, + or explicitly by the {{Navigator/updateAdInterestGroups()}} method. +
+ ++ +To append to a bidding signals per-interest group data map given an [=ordered map=] +|sourceMap|, an [=ordered set=] |igNames|, and an [=ordered map=] |destinationMap|: +1. [=map/For each=] |sourceKey| → |sourceValue| of |sourceMap|: + 1. If |igNames| does not [=set/contain=] |sourceKey|, [=iteration/continue=]. + 1. Let |perGroupData| be a new [=bidding signals per interest group data=]. + 1. Issue: handle priority vector + (WICG/turtledove#1144). + 1. If |sourceValue| is not an [=ordered map=], [=iteration/continue=]. + 1. If |sourceValue|["`updateIfOlderThanMs`"] is a {{double}}: + 1. Let |updateIfOlderThanMs| be |sourceValue|["`updateIfOlderThanMs`"]. + 1. If |updateIfOlderThanMs| < 600,000, set |updateIfOlderThanMs| to 600,000. + + Note: 600,000 milliseconds is 10 minutes. + 1. Set |perGroupData|'s [=bidding signals per interest group data/updateIfOlderThanMs=] to + |updateIfOlderThanMs|. + 1. If |perGroupData|'s [=bidding signals per interest group data/updateIfOlderThanMs=] is not + null, then [=map/set=] |destinationMap|[|sourceKey|] to |perGroupData|. + ++ + ++To fetch the current outstanding trusted signals batch given a +[=trusted bidding signals batcher=] |trustedBiddingSignalsBatcher|, a [=URL=] |signalsUrl|, +an [=origin=] |scriptOrigin|, an {{unsigned short}}-or-null |experimentGroupId|, +an [=origin=] |topLevelOrigin|, and a [=string=] |slotSizeQueryParam|: + + 1. If |signalsUrl| is null, return. + 1. Let |biddingSignalsUrl| be the result of [=building trusted bidding signals url=] with + |signalsUrl|, |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/keys=], + |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/ig names=], + |experimentGroupId|, |topLevelOrigin|, and |slotSizeQueryParam|. + 1. Let « |partialTrustedBiddingSignals|, |partialPerInterestGroupData|, |dataVersion| » be the + result of [=fetching trusted signals=] with |biddingSignalsUrl|, |scriptOrigin|, and true. + 1. If |partialTrustedBiddingSignals| is not null: + 1. [=map/For each=] |key| → |value| in |partialTrustedBiddingSignals|, [=map/set=] + |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/all trusted bidding + signals=][|key|] to |value|. + 1. [=set/For each=] |igName| of |trustedBiddingSignalsBatcher|'s [=trusted bidding signals + batcher/ig names=], [=map/set=] |trustedBiddingSignalsBatcher|'s [=trusted bidding signals + batcher/data versions=][|igName|] to |dataVersion|. + 1. [=Append to a bidding signals per-interest group data map=] with + |partialPerInterestGroupData|, |trustedBiddingSignalsBatcher|'s + [=trusted bidding signals batcher/ig names=], and |trustedBiddingSignalsBatcher|'s + [=trusted bidding signals batcher/all per interest group data=]. + 1. Otherwise, [=set/for each=] |igName| of |trustedBiddingSignalsBatcher|'s + [=trusted bidding signals batcher/ig names=]: + 1. [=map/Set=] |trustedBiddingSignalsBatcher|'s + [=trusted bidding signals batcher/no signals flags=][|igName|] to "fetch-failed". + ++ ++To batch or fetch trusted bidding signals given a [=trusted bidding signals batcher=] +|trustedBiddingSignalsBatcher|, [=interest group=] |ig|, a [=URL=] |signalsUrl|, +an [=origin=] |scriptOrigin| an {{unsigned short}}-or-null |experimentGroupId|, +an [=origin=] |topLevelOrigin|, and a [=string=] |slotSizeQueryParam|: + + 1. Let |igName| be |ig|'s [=interest group/name=]. + 1. If |signalsUrl| is null: + 1. [=map/Set=] |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/no signals flags=] + [|igName|] to "no-fetch". + 1. Return. + 1. [=map/Set=] |trustedBiddingSignalsBatcher|'s + [=trusted bidding signals batcher/no signals flags=][|igName|] to "no-fetch" + if |ig|'s [=interest group/trusted bidding signals keys=] is null or [=map/is empty=]. + + Note: An interest group with no trusted signals keys requests would still fetch and process + per-interest group data like priorityVector and + [=bidding signals per interest group data/updateIfOlderThanMs=], but it will get null passed in + to its bidding function. + + 1. Let |putativeKeys| be a [=set/clone=] of |trustedBiddingSignalsBatcher|'s + [=trusted bidding signals batcher/keys=]. + 1. Let |putativeIgNames| be a [=set/clone=] of |trustedBiddingSignalsBatcher|'s + [=trusted bidding signals batcher/ig names=]. + 1. Let |putativeLengthLimit| be |ig|'s [=interest group/max trusted bidding signals url length=]. + 1. If |ig|'s [=interest group/max trusted bidding signals url length=] is 0 or > + |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/length limit=], + then set |putativeLengthLimit| to |trustedBiddingSignalsBatcher|'s + [=trusted bidding signals batcher/length limit=]. + 1. If |ig|'s [=interest group/trusted bidding signals keys=] is not null, [=list/extend=] + |putativeKeys| with |ig|'s [=interest group/trusted bidding signals keys=]. + 1. [=list/Append=] |igName| to |putativeIgNames|. + 1. Let |biddingSignalsUrl| be the result of [=building trusted bidding signals url=] with + |signalsUrl|, |putativeKeys|, |putativeIgNames|, |experimentGroupId|, |topLevelOrigin|, and + |slotSizeQueryParam|. + 1. If [=URL serializer|serialized=] |biddingSignalsUrl|'s [=string/length=] ≤ |putativeLengthLimit|: + 1. Set |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/keys=] to |putativeKeys|. + 1. Set |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/ig names=] to + |putativeIgNames|. + 1. Set |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/length limit=] to + |putativeLengthLimit|. + 1. Otherwise: + 1. [=Fetch the current outstanding trusted signals batch=] given |trustedBiddingSignalsBatcher|, + |signalsUrl|, |scriptOrigin|, |experimentGroupId|, |topLevelOrigin|, |slotSizeQueryParam|. + 1. If |ig|'s [=interest group/trusted bidding signals keys=] is not null, set + |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/keys=] to a + [=list/clone=] of |ig|'s [=interest group/trusted bidding signals keys=]. + 1. Otherwise, set |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/keys=] to a + new [=list=]. + 1. Set |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/ig names=] to + « |igName| ». + 1. Set |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/length limit=] to + |ig|'s [=interest group/max trusted bidding signals url length=]. + ++Generated bid
A generated bid is a bid that needs to be scored by the seller. The bid is either the @@ -5633,6 +6417,7 @@ A bid debug reporting info is a [=struct=] with the following [=struc : interest group owner :: An [=origin=]. Matches [=interest group/owner=] of the interest group that was used to call `generateBid()` to produce this reporting information. + : bidder debug win report url :: Null or a [=URL=], initially null. Set by `generateBid()`'s {{InterestGroupBiddingAndScoringScriptRunnerGlobalScope/forDebuggingOnly}}'s @@ -5904,6 +6689,25 @@ An auction report info is a [=struct=] with the following [=struct/it experimenting with third party cookie deprecation (before they have been fully removed), the `forDebuggingOnly` reporting APIs are not downsampled, so they are still lists. + : real time reporting contributions map + :: A [=real time reporting contributions map=] + + +A real time reporting contributions map is an [=ordered map=] whose [=map/keys=] are +[=origins=] and whose [=map/values=] are [=lists=] of [=real time reporting contributions=]. + +A real time reporting contribution is a [=struct=] with the following [=struct/items=]: + ++ : bucket + :: A {{long}}. Must be greater than 0 and less than the sum of [=number of user buckets=] and + [=number of platform buckets=]. + : priority weight + :: A {{double}}. Must be greater than 0 and less than infinity, dictates the relative likelihood + of which bucket will get the contribution. + : latency threshold + :: Null or a [=duration=] in milliseconds. Initially null. + Reports when a latency (e.g., `generateBid()` execution latency) is greater than this threshold.
K-Anonymity Records