diff --git a/FLEDGE.md b/FLEDGE.md index 5d9cc7650..b8f2e681e 100644 --- a/FLEDGE.md +++ b/FLEDGE.md @@ -41,7 +41,8 @@ See [the Protected Audience API specification](https://wicg.github.io/turtledove - [5.2 Buyer Reporting on Render and Ad Events](#52-buyer-reporting-on-render-and-ad-events) - [5.2.1 Noised and Bucketed Signals](#521-noised-and-bucketed-signals) - [5.3 Currencies in Reporting](#53-currencies-in-reporting) - - [5.4 Losing Bidder Reporting](#54-losing-bidder-reporting) + - [5.4 Reporting IDs](#54-reporting-ids-in-reporting) + - [5.5 Losing Bidder Reporting](#55-losing-bidder-reporting) - [6. Additional Bids](#6-additional-bids) - [6.1 Auction Nonce](#61-auction-nonce) - [6.2 Negative Targeting](#62-negative-targeting) @@ -135,21 +136,21 @@ 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', + selectableBuyerAndSellerReportingIds: ['deal1', 'deal2', 'deal3'], + buyerReportingId: 'buyerSpecificInfo1', + buyerAndSellerReportingId: 'seatId', ...}], + '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'}, 'size4': {width: '100', height: '25'}}, 'sizeGroups:' {'group1': ['size1', 'size2', 'size3'], 'group2': ['size3', 'size4']}, - 'auctionServerRequestFlags': ['omit-ads'], + 'auctionServerRequestFlags': ['omit-ads', 'omit-user-bidding-signals'], }; const joinPromise = navigator.joinAdInterestGroup(myGroup); ``` @@ -159,7 +160,7 @@ The browser will only allow the `joinAdInterestGroup()` operation with the permi The returned `joinPromise` is resolved if the group is successfully joined, and rejected with an error if the join operation fails. The error message and the resolution time must _not_ depend on what interest groups a user is in, or any cross-origin browser state, apart from the results of the .well-known fetch, to avoid leaking any data across sites. -There is a complementary API `navigator.leaveAdInterestGroup(myGroup)` which looks only at `myGroup.name` and `myGroup.owner`. As with join calls, `leaveAdInterestGroup()` also returns a promise. As a special case to support in-ad UIs, invoking `navigator.leaveAdInterestGroup()` from inside an ad that is being targeted at a particular interest group will cause the browser to leave that group, irrespective of permission policies. Note that calling `navigator.leaveAdInterestGroup()` without arguments inside a [component ad](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#34-ads-composed-of-multiple-pieces) frame isn't supported until Chrome M120. Starting from Chrome M120, calling `navigator.leaveAdInterestGroup()` without arguments inside a component ad frame is supported. The ad component frame is required to be same-origin with the interest group owner for the leave to succeed, same as calling `leaveAdInterestGroup()` without arguments in a non-ad-component frame. +There is a complementary API `navigator.leaveAdInterestGroup(myGroup)` which looks only at `myGroup.name` and `myGroup.owner`. As with join calls, `leaveAdInterestGroup()` also returns a promise. As a special case to support in-ad UIs, invoking `navigator.leaveAdInterestGroup()` from inside an ad that is being targeted at a particular interest group will cause the browser to leave that group, irrespective of permission policies. Note that calling `navigator.leaveAdInterestGroup()` without arguments inside a [component ad](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#34-ads-composed-of-multiple-pieces) frame isn't supported until Chrome M120. Starting from Chrome M120, calling `navigator.leaveAdInterestGroup()` without arguments inside a component ad frame is supported. The ad component frame is required to be same-origin with the interest group owner for the leave to succeed, same as calling `leaveAdInterestGroup()` without arguments in a non-ad-component frame. There is a related API `navigator.clearOriginJoinedAdInterestGroups(owner, [])` that leaves all interest groups owned by `owner` that were joined on the current top-level frame's origin, and also returns a Promise. The `[]` argument is an optional list of interest group names that will not be left, and if not present, it will act as if an empty array was passed. This method has no effect on joined interest groups owned by `owner` that were most recently joined on different top-level origins. @@ -181,7 +182,7 @@ The `updateURL` provides a mechanism for the group's owner to update the attribu of the interest group: any new values returned in this way overwrite the values previously stored (except that the `name` and `owner` cannot be changed, and `prioritySignalsOverrides` will be merged with the previous value, with `null` -meaning a value should be removed from the interest group's old dictionary). The HTTP request +meaning a value should be removed from the interest group's old dictionary). The HTTP request will not include any metadata, so data such as the interest group `name` should be included within the URL. Note that the lifetime of an Interest Group is not affected by the update mechanism — ad targeting based on a person's activity on a site remains limited to 30 days after the most recent site visit. @@ -240,26 +241,67 @@ The `ads` list contains the various ads that the interest group might show. Eac * `adRenderId`: A short [DOMString](https://webidl.spec.whatwg.org/#idl-DOMString) up to 12 characters long serving as an identifier for this ad in this interest group. When this field is specified it will be sent instead of the full ad object for [B&A server auctions](https://github.com/WICG/turtledove/blob/main/FLEDGE_browser_bidding_and_auction_API.md). - * `buyerAndSellerReportingId`: If set, the value is used instead of the interest group name or `buyerReportingId` for reporting in `reportWin` and `reportResult`. Note that this field needs to be jointly k-anonymous with the interest group owner, bidding script URL, and render URL to be provided to these reporting fuctions (in the same way that the interest group name would have needed to be). - - * `buyerReportingId`: If set, the value is used instead of the interest group name for reporting in `reportWin`. Note that this field needs to be jointly k-anonymous with the interest group owner, bidding script URL, and render URL to be provided to these reporting fuctions (in the same way that the interest group name would have needed to be). + * `selectableBuyerAndSellerReportingIds`: An array of + [USVString](https://webidl.spec.whatwg.org/#idl-USVString)s, one of which may be + selected by `generateBid()` to be reported to `reportWin()` and `reportResult()` + along with `buyerAndSellerReportingId` and `buyerReportingId`. + When `generateBid()` selects a value from `selectablebuyerAndSellerReportingIds`, + the resulting bid may only win the auction if the selected value is jointly k-anonymous + along with `buyerAndSellerReportingId`, `buyerReportingId`, the interest group owner, + bidding script URL, and render URL. See + [Reporting IDs](#54-reporting-ids-in-reporting) for more details. + + * `buyerAndSellerReportingId`: A + [USVString](https://webidl.spec.whatwg.org/#idl-USVString), no character limit. + If set and `selectableBuyerAndSellerReportingIds` is + unset or not selected, the value is used instead of the interest group name or `buyerReportingId` + for reporting in `reportWin()` and `reportResult()`. Note that this field needs to + be jointly k-anonymous with the interest group owner, bidding script URL, and + render URL to be provided to these reporting functions (in the same way that the + interest group name would have needed to be). If set and a value from + `selectableBuyerAndSellerReportingIds` is selected by `generateBid()`, the value of + this is reported to `reportWin()` and `reportResult()` along with the selected + `selectablebuyerAndSellerReportingIds` and `buyerReportingId`. When `generateBid()` + selects a value from `selectablebuyerAndSellerReportingIds`, the resulting bid may + only win the auction if the selected value is jointly k-anonymous along with + `buyerAndSellerReportingId`, `buyerReportingId`, the interest group owner, bidding + script URL, and render URL. See + [Reporting IDs](#54-reporting-ids-in-reporting) for more details. + + * `buyerReportingId`: A [USVString](https://webidl.spec.whatwg.org/#idl-USVString), + no character limit. If set, `buyerAndSellerReportingId` is unset, and + `selectableBuyerAndSellerReportingIds` is unset or not selected, the + value is used instead of the interest group name for reporting in `reportWin`. Note + that this field needs to be jointly k-anonymous with the interest group owner, + bidding script URL, and render URL to be provided to these reporting functions (in + the same way that the interest group name would have needed to be). If set and a + value from `selectableBuyerAndSellerReportingIds` is selected by `generateBid()`, + the value of this is reported to `reportWin()` along with the + selected `selectablebuyerAndSellerReportingIds` and `buyerAndSellerReportingId`. + When `generateBid()` selects a value from `selectablebuyerAndSellerReportingIds`, + the resulting bid may only win the auction if the selected value is jointly + k-anonymous along with `buyerAndSellerReportingId`, `buyerReportingId`, the + interest group owner, bidding script URL, and render URL. See + [Reporting IDs](#54-reporting-ids-in-reporting) for more details. * `metadata`: Arbitrary metadata that can be used at bidding time. - * `allowedReportingOrigins`: A list of up to 10 destination origins allowed to receive [registered macro values in reporting](https://github.com/WICG/turtledove/blob/main/Fenced_Frames_Ads_Reporting.md#registeradmacro). All origins must be HTTPS and [attested for Protected Audience API](https://github.com/privacysandbox/attestation#the-privacy-sandbox-enrollment-attestation-model). + * `allowedReportingOrigins`: A list of up to 10 destination origins allowed to receive [registered macro values in reporting](https://github.com/WICG/turtledove/blob/main/Fenced_Frames_Ads_Reporting.md#registeradmacro). All origins must be HTTPS and [attested for Protected Audience API](https://github.com/privacysandbox/attestation#the-privacy-sandbox-enrollment-attestation-model). Entries are [USVString](https://webidl.spec.whatwg.org/#idl-USVString), no character limit. Invalid URL, non HTTPS, or list size greater than 10, will result in failure to join the interest group. The `adComponents` field contains the various ad components (or "products") that can be used to construct ["Ads Composed of Multiple Pieces"](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#34-ads-composed-of-multiple-pieces)). Similar to the `ads` field, each entry is an object that includes a `renderURL` and optional `adRenderId`, and `metadata` fields. Thanks to `ads` and `adComponents` being separate fields, the buyer is able to update the `ads` field via the `updateURL` without losing `adComponents` stored in the interest group. The `adSizes` field (optionally) contains a dictionary of named ad sizes. Each size has the format `{width: widthVal, height: heightVal}`, where the values can have either pixel units (e.g. `100` or `'100px'`) or screen dimension coordinates (e.g. `100sw` or `100sh`). For example, the size `{width: '100sw', height: 50}` describes an ad that is the width of the screen and 50 pixels tall. The size `{width: '100sw', height: '200sw'}` describes an ad that is the width of the screen and has a 1:2 aspect ratio. Sizes with screen dimension coordinates are primarily intended for screen-width ads on mobile devices, and may be restricted in certain contexts (to be determined) for privacy reasons. -The `sizeGroups` field (optionally) contains a dictionary of named lists of ad sizes. Each ad declared above must specify a size group, saying which sizes it might be loaded at. Each named ad size is also considered a size group, so you don't need to manually define singleton size groups; for example see the `sizeGroup: 'size3'` code above. +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: - * The `omit-ads` enumeration causes the request to omit the `ads` and `adComponents` fields for -this interest group from the auction blob. + * The `omit-ads` enumeration causes the request to omit the `ads` and + `adComponents` fields for this interest group from the auction blob. + * The `omit-user-bidding-signals` enumeration causes the request to omit the + `userBiddingSignals` field for this interest group from the auction blob. * The `include-full-ads` enumeration causes the request to include the full ad object in place of anywhere in the request where a plain `adRenderId` would have been sent (such as in the `ads` and `adComponents` fields as well as `prevWins`). Note that `include-full-ads` is not compatible @@ -273,12 +315,12 @@ same-origin with `owner`. Additionally, to be used in an auction, the HTTP respo header `Ad-Auction-Allowed: true` to ensure they are allowed to be used for loading Protected Audience resources. The `trustedBiddingSignalsURL` must also not have a [query](https://url.spec.whatwg.org/#concept-url-query). (See 6.13 [here](https://wicg.github.io/turtledove/#dom-navigator-joinadinterestgroup)). -The `renderUrl` property of an `ad` must be also be a valid and credentialless HTTPs URL, but does _not_ +The `renderURL` property of an `ad` must be also be a valid and credentialless HTTPS URL, but does _not_ 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. @@ -468,7 +510,7 @@ While `joinAdInterestGroup()` has strict requirements about the calling origin matching the interest group’s owner (or a prescribed delegate), `runAdAuction()` does not require the calling origin matches the seller’s origin. This means there can be times when `runAdAuction()` is not called from the seller’s origin, -for example: +for example: 1. when a top-level seller calls `runAdAuction()` from the publisher’s site context, or @@ -496,7 +538,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 @@ -534,6 +582,8 @@ The function gets called once for each candidate ad in the auction. The argumen 'biddingDurationMsec': 12, 'bidCurrency': 'USD', /* bidCurrency returned by generateBid, or '???' if none */ 'dataVersion': 1, /* Data-Version value from the trusted scoring signals server's response */ + 'selectedBuyerAndSellerReportingId': 'deal2', /* Value returned by generateBid. */ + 'buyerAndSellerReportingId': 'seatId' } ``` * directFromSellerSignals is an object that may contain the following fields: @@ -674,7 +724,7 @@ Note that only the `Ad-Auction-Signals` response header from the server will onl Like `directFromSellerSignals`, `directFromSellerSignalsHeaderAdSlot` may be passed as a promise that resolves to the ad slot string -- the auction perform loading, but delays execution until the promise is resolved. -The signals are only guaranteed to be available to `navigator.runAdAuction()` after the `fetch()` promise has resolved. Therefore, to avoid races, either `runAdAuction()` should be called after resolving the `fetch()` promise, or `directFromSellerSignalsHeaderAdSlot` should be passed a promise that only resolves after the `fetch()` promise resolves. +The signals are only guaranteed to be available to `navigator.runAdAuction()` after the `fetch()` promise has resolved. Therefore, to avoid races, either `runAdAuction()` should be called after resolving the `fetch()` promise, or `directFromSellerSignalsHeaderAdSlot` should be passed a promise that only resolves after the `fetch()` promise resolves. ### 3. Buyers Provide Ads and Bidding Functions (BYOS for now) @@ -701,7 +751,7 @@ Buyers may want to make on-device decisions that take into account real-time dat The base URL `https://www.kv-server.example/getvalues` comes from the interest group's `trustedBiddingSignalsURL`, the hostname of the top-level webpage where the ad will appear `publisher.com` is provided by the browser, `experimentGroupId` comes from `perBuyerExperimentGroupIds` if provided, `keys` is a list of `trustedBiddingSignalsKeys` strings, and `interestGroupNames` is a list of the names of the interest groups that data is being fetched for. `trustedBiddingSignalsSlotSizeMode` is one of `none` (which is the default), `slot-size`, and `all-slots-requested-sizes`. In the second case, `&slotSize=,` is appended to the URL, where width and height are the normalized width and height from the `requestedSize` of the (component) auction's AuctionConfig. "Normalized" means units are always appended, and trailing zeros are removed, so {width: "62.50sw", height: "10.0"} becomes "62.5sw,10px". In the `all-slots-requested-sizes` case, `&allSlotsRequestedSizes=,,,,...` is appended, where all sizes are taken from the (component) auction's `allSlotsRequestedSizes` value. If the corresponding value is not present in the auction configuration, no value is appended. -The requests may be coalesced (for efficiency) across a certain number of interest groups that share a `trustedBiddingSignalsURL` and `trustedBiddingSignalsSlotSizeMode` (which means they share an owner). The number of interest groups coalesced into a single request is limited by their `maxTrustedBiddingSignalsURLLength` fields. For instance, if an interest group has a `maxTrustedBiddingSignalsURLLength` of 1000, it means that the length of the trusted bidding signals request URL for this interest group cannot exceed 1000 characters. This prevents requests from being dropped by trusted signals servers due to oversized URLs. If an interest group wants an infinite length for the request URL, it can specify 0 for the `maxTrustedBiddingSignalsURLLength`. +The requests may automatically be coalesced (for efficiency) across a certain number of interest groups that share a `trustedBiddingSignalsURL` and `trustedBiddingSignalsSlotSizeMode` (which means they share an owner). [Chrome coalesces requests for all interest groups that share an executor.](PA_implementation_overview.md#trusted-signals-fetches--reprioritization) The number of interest groups coalesced into a single request is limited by their `maxTrustedBiddingSignalsURLLength` fields. For instance, if an interest group has a `maxTrustedBiddingSignalsURLLength` of 1000, it means that the length of the trusted bidding signals request URL for this interest group cannot exceed 1000 characters. This prevents requests from being dropped by trusted signals servers due to oversized URLs. If an interest group wants an infinite length for the request URL, it can specify 0 for the `maxTrustedBiddingSignalsURLLength`. The response from the server should be a JSON object of the form: @@ -731,7 +781,7 @@ The `perInterestGroupData` dictionary contains optional data for interest groups The `updateIfOlderThanMs` optional field specifies that the interest group should be updated via the `updateURL` mechanism (see the [interest group attributes](#12-interest-group-attributes) section) if the interest group hasn't been joined or updated in a duration of time exceeding `updateIfOlderThanMs` milliseconds. Updates that ended in failure, either parse or network failure, are not considered to increment the last update or join time. An `updateIfOlderThanMs` that's less than 10 minutes will be clamped to 10 minutes. -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 as [`trustedBiddingSignalsURL`](#31-fetching-real-time-data-from-a-trusted-server), with the base URL coming from the `trustedScoringSignalsURL` property of the seller's auction configuration object. The parameter `experimentGroupId` comes from `sellerExperimentGroupId` in the auction configuration if provided. However, the URL has two sets of keys: "renderUrls=url1,url2,..." and "adComponentRenderUrls=url1,url2,..." for the main and adComponent renderURLs bids offered in the auction. Note that the query params use "Urls" instead of "URLs". It is up to the client how and whether to aggregate the fetches with the URLs of multiple bidders. +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 as [`trustedBiddingSignalsURL`](#31-fetching-real-time-data-from-a-trusted-server), with the base URL coming from the `trustedScoringSignalsURL` property of the seller's auction configuration object. The parameter `experimentGroupId` comes from `sellerExperimentGroupId` in the auction configuration if provided. However, the URL has two sets of keys: "renderUrls=url1,url2,..." and "adComponentRenderUrls=url1,url2,..." for the main and adComponent renderURLs bids offered in the auction. Note that the query params use "Urls" instead of "URLs". It is up to the client how and whether to aggregate the fetches with the URLs of multiple bidders. Similarly to `trustedBiddingSignalsURL`, scoring signals requests may also be coalesced across a certain number of bids that share a `trustedScoringSignalsURL`. The number of bids in a single request is limited by the auction configuration's `maxTrustedScoringSignalsURLLength` field. For example, if an auction configuration has a `maxTrustedScoringSignalsURLLength` of 1000, it means that the length of each trusted scoring signals request URL for this auction cannot exceed 1000 characters. If an auction configuration wants an infinite length for the request URL, it can specify 0 for the `maxTrustedScoringSignalsURLLength`. @@ -860,7 +910,8 @@ generateBid(interestGroup, auctionSignals, perBuyerSignals, 'allowComponentAuction': false, 'targetNumAdComponents': 3, 'numMandatoryAdComponents': 1, - 'modelingSignals': 123}; + 'modelingSignals': 123, + 'selectedBuyerAndSellerReportingId': 'deal2'}; } ``` @@ -871,6 +922,10 @@ 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. This used when the trusted server is same-origin with the buyer's script. @@ -912,11 +967,11 @@ The output of `generateBid()` contains the following fields: * bid: A numerical bid that will enter the auction. The seller must be in a position to compare bids from different buyers, therefore bids must be in some seller-chosen unit (e.g. "USD per thousand"). If the bid is zero or negative, then this interest group will not participate in the seller's auction at all. With this mechanism, the buyer can implement any advertiser rules for where their ads may or may not appear. While this returned value is expected to be a JavaScript Number, internal calculations dealing with currencies should be done with integer math that more accurately represent powers of ten. * bidCurrency: (optional) The currency for the bid, used for [currency-checking](#36-currency-checking). * render: A dictionary describing the creative that should be rendered if this bid wins the auction. This includes: - * url: The creative's URL. - * width: The creative's width. This size will be matched against the declaration in the interest group and substituted into any ad size macros present in the ad creative URL. When the ad is loaded in a fenced frame, the fenced frame's inner frame (i.e. the size visible to the ad creative) will be frozen to this size, and it will be unable to see changes to the rame size made by the embedder. + * url: The creative's URL. This must match the `renderURL` of an ad in the interest group's `ads` list, otherwise the bid is ignored. + * width: The creative's width. This size will be matched against the declaration in the interest group and substituted into any ad size macros present in the ad creative URL. When the ad is loaded in a fenced frame, the fenced frame's inner frame (i.e. the size visible to the ad creative) will be frozen to this size, and it will be unable to see changes to the frame size made by the embedder. * height: The creative's height. See elaboration in `width` above. - - Optionally, if you don't want to hook into interest group size declarations (e.g., if you don't want to use size macros), you can have `render` be just the URL, rather than a dictionary with `url` and `size`. + + Optionally, if you don't want to hook into interest group size declarations (e.g., if you don't want to use size macros), you can have `render` be just the URL, rather than a dictionary with `url`, `width` and `height`. * adComponents: (optional) A list of up to 20 (in process of being increased to 40 starting from M122) adComponent strings from the InterestGroup's adComponents field. Each value must match one of `interestGroup`'s `adComponent`'s `renderURL` and sizes exactly. This field must not be present if `interestGroup` has no `adComponent` field. It is valid for this field not to be present even when `adComponents` is present. (See ["Ads Composed of Multiple Pieces"](#34-ads-composed-of-multiple-pieces) below.) * allowComponentAuction: If this buyer is taking part of a component auction, this value must be present and true, or the bid is ignored. This value is ignored (and may be absent) if the buyer is part of a top-level auction. * modelingSignals (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. @@ -924,6 +979,17 @@ The output of `generateBid()` contains the following fields: 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-flexible-component-ad-selection-considering-k-anonymity) for more details. +* selectedBuyerAndSellerReportingId: (optional) A string from the interest + group's ad's `selectableBuyerAndSellerReportingIds` array; if it's not in + the array, the bid is ignored. If present, this is reported to `reportWin()` + and `reportResult()` along with `buyerAndSellerReportingId` and + `buyerReportingId`; the resulting bid may only win the auction if the + selected value is jointly k-anonymous along with `buyerAndSellerReportingId`, + `buyerReportingId`, the interest group owner, bidding script URL, and render + URL. If present, it indicates the reporting of this bid would not operate + correctly were the `selectedBuyerAndSellerReportingId` not presented to + `reportWin()` and `reportResult()`. See + [Reporting IDs](#54-reporting-ids-in-reporting) for more details. In case returning multiple bids is supported by the implementation in use, `generateBid` may also return up to `browserSignals.multiBidLimit` valid bid @@ -931,9 +997,12 @@ objects of the format above in an array. Note: Chrome currently imposes an internal limit of 100 for the length of returned bids sequences. -If none of the produced bids pass the k-anonymity checks, `generateBid` will be +If none of the produced bids pass the k-anonymity checks (the check that +includes the render `url` and, when `selectedBuyerAndSellerReportingId` is +returned, the check that includes its value also), `generateBid` will be re-run with the input `interestGroup` filtered to contain only k-anonymous ads -and component ads. Such re-runs are limited to returning only a single bid, +and component ads and `selectableBuyerAndSellerReportingIds`. Such re-runs are +limited to returning only a single bid, even if multiple bid support is otherwise on, so they will have `browserSignals.multiBidLimit === 1`, regardless of the value of `perBuyerMultiBidLimits`. @@ -972,7 +1041,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 @@ -1094,7 +1163,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. @@ -1103,7 +1172,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. ``` @@ -1113,6 +1182,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: @@ -1122,7 +1193,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', @@ -1138,6 +1209,8 @@ The arguments to this function are: 'modifiedBid': modifiedBidValue, 'highestScoringOtherBid': highestScoringOtherBidValue, 'highestScoringOtherBidCurrency': 'EUR' + 'buyerAndSellerReportingId': 'seatId', + 'selectedBuyerAndSellerReportingId': 'deal2', } ``` * `bidCurrency` and `highestScoringOtherBidCurrency` provide (highly redacted) information on what currency the corresponding numbers are in. Please refer to the section on [Currencies in Reporting](#53-currencies-in-reporting) for more details. @@ -1149,7 +1222,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 @@ -1168,9 +1241,10 @@ The arguments to this function are: * auctionSignals and perBuyerSignals: As in the call to `generateBid()` for the winning interest group. * 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: +* browserSignals: Similar to the argument to `reportResult()` above, though without the seller's desirability score, but with additional `adCost`, `seller`, `madeHighestScoringOtherBid` and potentially `buyerReportingId` or `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 `buyerReportingId` may be included if selected (see [Reporting IDs](#54-reporting-ids-in-reporting) for selection details) and the tuple of `buyerReportingId`, interest group owner, 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 `interestGroupName` may be included if selected (see [Reporting IDs](#54-reporting-ids-in-reporting) for selection details) and 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. @@ -1235,7 +1309,104 @@ The following table summarizes which APIs get original and which get converted b | Private Aggregation `winning-bid` | Original value | Converted value | | Private Aggregation `highest-scoring-other-bid` | Original value | Converted value | -#### 5.4 Losing Bidder Reporting +#### 5.4 Reporting IDs + +Protected Audience provides several interest group fields that can be used to report details about a bid. These fields needs to +be jointly k-anonymous with the interest group owner, bidding script URL, and render URL to be provided to the reporting functions. +Which of `selectableBuyerAndSellerReportingIds`, `buyerAndSellerReportingId`, `buyerReportingId`, and the interest group name gets +passed to `reportWin()` and `reportResult()` is determined by the browser with the following logic: + +* If `selectedBuyerAndSellerReportingId` in bid: + * Then `selectedBuyerAndSellerReportingId`, `buyerAndSellerReportingId` (if present in interest group), and `buyerReportingId` + (if present in interest group) will all be available to reporting. +* Otherwise, if `buyerAndSellerReportingId` defined in interest group: `buyerAndSellerReportingId` available to reporting. +* Otherwise, if `buyerReportingId` defined in interest group: `buyerReportingId` available to reporting. +* Otherwise, interest group `name` available to reporting. + +Here's a table representation of the above logic: (`selectableBuyerAndSellerReportingIds` abbreviated to selectableBASRI, +`buyerAndSellerReportingId` abbreviated to BASRI, `buyerReportingId` abbreviated to BRI) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
When interest group contains:then reports get:
selectableBASRIBASRIBRIreportWin()reportResult()
yes and in bidoptionaloptionalselectableBASRI, BASRI, BRIselectableBASRI, BASRI
no or not in bidyesignoredBASRIBASRI
no or not in bidnoyesBRI
no or not in bidnonointerest group name
+ +When `selectableBuyerAndSellerReportingIds` is set, `generateBid()` is passed all +reporting IDs in each entry in the interest group's `ads` list, though in cases where `generateBid()` +is re-run, after the first invocation didn't produce any bids with ads that passed the k-anonymity checks, +`selectableBuyerAndSellerReportingIds` that don't pass the k-anonymity check will not be present in the interest group. + +For bids with a value for `selectedbuyerAndSellerReportingId`, `scoreAd` is passed +the same reporting IDs as would be passed to `reportResult()` +(`selectedBuyerAndSellerReportingId` and `buyerAndSellerReportingId`). +To ensure accurate reporting, a bid with a value for `selectedbuyerAndSellerReportingId` may only win the auction if the value of +`selectedbuyerAndSellerReportingId` is jointly k-anonymous along with `buyerAndSellerReportingId`, `buyerReportingId`, the interest +group owner, bidding script URL, and render URL, so that `reportResult()` will always receive the value of +`selectedbuyerAndSellerReportingId` for bids that provided a value. + +One use for these reporting IDs is to facilitate deals (private marketplace) in Protected Audience auctions. Here's a couple +examples of using the reporting IDs to convey deals used in bids. In these examples, the 's' prefix might be used to connote seat +ID and the 'd' prefix might connote deal ID. + +If the same seat ID is always presented for bids on an ad: +``` +joinAdInterestGroup({... + 'ads': [{renderURL: Ad1URL, + buyerReportingId: 'buyerInfo123', + buyerAndSellerReportingId: 's456', + selectableBuyerAndSellerReportingIds: [ 'd123', 'd234', 'd345' ], + ...}) +``` +If different seat IDs are presented for bids on an ad: +``` +joinAdInterestGroup({... + 'ads': [{renderURL: Ad1URL, + buyerReportingId: 'buyerInfo123', + selectableBuyerAndSellerReportingIds: [ 'd123s456', 'd234s567', 'd345s567' ], + ...}) +``` + +#### 5.5 Losing Bidder Reporting We also need to provide a mechanism for the _losing_ bidders in the auction to learn aggregate outcomes. Certainly they should be able to count the number of times they bid, and losing ads should also be able to learn (in aggregate) some seller-provided information about e.g. the auction clearing price. Likewise, a reporting mechanism should be available to buyers who attempted to bid with a creative that had not yet reached the k-anonymity threshold. @@ -1440,8 +1611,8 @@ For additional bids that win the auction, event-level win reporting is supported #### 7.1 forDebuggingOnly (fDO) APIs -`generateBid()` and `scoreAd()` may call a `forDebuggingOnly.reportAdAuctionLoss()` API for event-level reporting for losing bids, which takes a single string argument representing a URL. -If the bid being generated or scored loses the auction, the URL will be fetched. These worklets may also call a `forDebuggingOnly.reportAdAuctionWin()` API which operates similarly to `forDebuggingOnly.reportAdAuctionLoss()` API but only fetches the URL after a winning bid or score. The forDebuggingOnly APIs support text placeholders `${}` in the reporting URL's query parameters, which will be replaced with the corresponding value from the auction (see [7.1.1 Post Auction Signals](#711-post-auction-signals)). Callers of these APIs 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. +`generateBid()` and `scoreAd()` may call a `forDebuggingOnly.reportAdAuctionLoss()` API for event-level reporting for losing bids, which takes a single string argument representing a URL. +If the bid being generated or scored loses the auction, the URL will be fetched. These worklets may also call a `forDebuggingOnly.reportAdAuctionWin()` API which operates similarly to `forDebuggingOnly.reportAdAuctionLoss()` API but only fetches the URL after a winning bid or score. The forDebuggingOnly APIs support text placeholders `${}` in the reporting URL's query parameters, which will be replaced with the corresponding value from the auction (see [7.1.1 Post Auction Signals](#711-post-auction-signals)). Callers of these APIs 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. ``` generateBid(interestGroup, auctionSignals, perBuyerSignals, @@ -1462,7 +1633,7 @@ In order to accomplish our dual goals of helping with adoption and preserving us The URL passed to forDebuggingOnly.reportAdAuctionLoss() or forDebuggingOnly.reportAdAuctionWin() 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). -##### 7.1.1 Post Auction Signals +##### 7.1.1 Post Auction Signals A post auction signal is a signal which is only available after the auction completes, such as the highest scoring other bid. The forDebuggingOnly APIs support the text placeholders below, which will be replaced with the corresponding value from the auction when found in the reporting URL's query parameters. diff --git a/FLEDGE_Key_Value_Server_API.md b/FLEDGE_Key_Value_Server_API.md index 8ccf7df93..a4567ba13 100644 --- a/FLEDGE_Key_Value_Server_API.md +++ b/FLEDGE_Key_Value_Server_API.md @@ -73,10 +73,11 @@ We will use [Oblivious HTTP](https://datatracker.ietf.org/doc/draft-ietf-ohai-oh * 0x0001 HKDF-SHA256 for KDF (key derivation functions) * AES256GCM for AEAD scheme. -* The OHTTP request has media type “message/ad-auction-trusted-signals-request; v=2.0” -* The OHTTP response has media type “message/ad-auction-trusted-signals-response; v=2.0” +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” -The version information is of the format `major.minor` where `major` and `minor` are integers. +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. @@ -378,6 +379,10 @@ The content of each compressed blob is a CBOR list of partition outputs. This ob "description": "Unique id of the partition from the request", "type": "unsigned integer" }, + "dataVersion" { + "description": "An optional field to indicate the state of the data that generated this response, which will then be available in bid generation/scoring and reporting.", + "type": "unsigned integer" + }, "keyGroupOutputs": { "type": "array", "items": { @@ -430,6 +435,7 @@ Example: [ { "id": 0, + "dataVersion": 102, "keyGroupOutputs": [ { "tags": [ @@ -437,7 +443,7 @@ Example: ], "keyValues": { "InterestGroup1": { - "value": "{\"priorityVector\":{\"signal1\":1}}" + "value": "{\"priorityVector\":{\"signal1\":1},\"updateIfOlderThanMs\": 10000}" } } }, @@ -484,6 +490,10 @@ For values for keys from the `interestGroupNames` namespace, they must conform t "type": "number" } } + }, + "updateIfOlderThanMs": { + "description": "This optional field specifies that the interest group should be updated if the interest group hasn't been joined or updated in a duration of time exceeding `updateIfOlderThanMs` milliseconds. Updates that ended in failure, either parse or network failure, are not considered to increment the last update or join time. An `updateIfOlderThanMs` that's less than 10 minutes will be clamped to 10 minutes.", + "type": "unsigned integer" } } } @@ -496,7 +506,8 @@ Example: "priorityVector": { "signal1": 1, "signal2": 2 - } + }, + "updateIfOlderThanMs": 10000 } ``` diff --git a/FLEDGE_browser_bidding_and_auction_API.md b/FLEDGE_browser_bidding_and_auction_API.md index 641cc93d9..bdbc3e471 100644 --- a/FLEDGE_browser_bidding_and_auction_API.md +++ b/FLEDGE_browser_bidding_and_auction_API.md @@ -1,29 +1,49 @@ > 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 Browser Bidding & Auction API +# Protected Audience Browser Bidding & Auction API ## Background -This document seeks to propose an API for web pages to perform FLEDGE auctions using Bidding and Auction (B&A) servers running in Trusted Execution Environments (TEE), as was announced [here](https://developer.chrome.com/blog/bidding-and-auction-services-availability/). This document seeks to document the web-exposed JavaScript API. The browser is responsible for formatting the data sent to the B&A servers using the B&A server API documented in [this explainer](https://github.com/privacysandbox/fledge-docs/blob/main/bidding_auction_services_api.md). +This document seeks to propose an API for web pages to perform Protected Audience auctions using Bidding and Auction (B&A) servers running in Trusted Execution Environments (TEE), as was announced [here](https://developer.chrome.com/blog/bidding-and-auction-services-availability/). This document seeks to document the web-exposed JavaScript API. The browser is responsible for formatting the data sent to the B&A servers using the B&A server API documented in [this explainer](https://github.com/privacysandbox/fledge-docs/blob/main/bidding_auction_services_api.md). -## Steps to perform a FLEDGE auction using B&A +## Steps to perform a Protected Audience auction using B&A ### Step 1: Get auction blob from browser -To execute an on-server FLEDGE auction, sellers begin by calling `navigator.getInterestGroupAdAuctionData()` with returns a `Promise`: +To execute an on-server Protected Audience auction, sellers begin by calling `navigator.getInterestGroupAdAuctionData()` with returns a `Promise`: ```javascript const auctionBlob = navigator.getInterestGroupAdAuctionData({ // ‘seller’ works the same as for runAdAuction. 'seller': 'https://www.example-ssp.com', // 'coordinatorOrigin' of the TEE coordinator, defaults to - //'https://publickeyservice.gcp.privacysandboxservices.com' (for now). Used to - // select which key to use for the encrypted blob. Will eventually be - // required. + //'https://publickeyservice.pa.gcp.privacysandboxservices.com/' (for now). Used to + // select which coordinator to use for fetching the key used to encrypt the request + // blob. Will eventually be required. 'coordinatorOrigin': - 'https://publickeyservice.gcp.privacysandboxservices.com', + 'https://publickeyservice.pa.gcp.privacysandboxservices.com/', + // 'requestSize' the affects the size of the returned request (optional). + 'requestSize': 51200, + // 'perBuyerConfig' specifies per-buyer options for size optimizations when + // constructing the blob (optional). + 'perBuyerConfig': { + 'https://buyer1.origin.example.com': { + // 'targetSize' specifies the size of the blob to devote to this buyer + // (optional). + "targetSize": 8192, + }, + 'https://buyer2.origin.example.com': {} + } }); ``` + +The `seller` field will be checked to ensure it matches the `seller` specified +in the auction configuration passed to `runAdAuction()` with the response. The +`coordinatorOrigin` selects which set of TEE keys should be used to encrypt this +request. The `coordinatorOrigin` must be a coordinator that is known to Chrome. +The `requestSize` and `perBuyerConfig` fields are described in more detail in +the [Request Size and Configuration](#request-size-and-configuration) section below. + The returned `auctionBlob` is a Promise that will resolve to an `AdAuctionData` object. This object contains `requestId` and `request` fields. The `requestId` contains a UUID that needs to be presented to `runAdAuction()` along with the response. The `request` field is a `Uint8Array` containing the information needed for the [ProtectedAudienceInput](https://github.com/privacysandbox/fledge-docs/blob/main/bidding_auction_services_api.md#protectedaudienceinput) in a `SelectAd` B&A call, @@ -130,6 +150,47 @@ const myAuctionConfig = { Note that since the `serverResponse` field in the config is a promise it is possible to run the on-device auction in parallel with the B&A auction. +## Request Size and Configuration + +### Request Size Controls +The `requestSize` field provided to `navigator.getInterestGroupAdAuctionData()` +can be used to specify the maximum size of the returned request. If the +`perBuyerConfig` field is not present and the blob fits into a +size bucket smaller than `requestSize` then that size will be used instead. + +If the `perBuyerConfig` field is specified and non-empty, the returned encrypted +blob will be exactly `requestSize` bytes long unless there was an error. If an error +occured then the returned blob will be 0 size and the function may throw an error. + +### Buyer Controls +If the `perBuyerConfig` field is specified then the blob will only include the +interest groups for the buyers specifically listed. If `requestSize` is not +specified then the sum of the `targetSize` of each buyer will be used for the +`requestSize`. Interest groups for each buyer will be added to the request -- in +decreasing order by the interest group's `priority` field -- until the next +interest group does not fit. + +Space can be allocated between different buyers in several different modes, depending +on what options are specified in the `perBuyerConfig`: + +1. Equally - Space is divided equally between buyers when `targetSize` is not specified. + +2. Proportionally - Space is assigned to buyers using `targetSize` as the + weight. So a buyer with a `targetSize` of 2 will can use twice as much space + as a buyer with a `targetSize` of 1. If all buyers have a `targetSize` + specified then proportional mode is used. + +3. Fixed/Mixed - Space is first allocated to buyers with `targetSize` specified. + They get up to `targetSize` bytes. The remaining space is divided equally + between the buyers that did not have `targetSize` specified. + +The browser will first process buyers with `targetSize` specified before +processing buyers without that field specified. Within either of these groups +buyers are processed in random order. Space that was allocated to a buyer but +was not used is divided up among remaining buyers based on the active allocation +mode. The browser may, as an optimization, calculate and use the maximum length +used by a buyer to better inform its allocation strategy. + ## Privacy Considerations The blobs sent to and received from the B&A servers can contain data that could be used to re-identify the user across different web sites. To prevent this data from being used to join the user’s cross-site identities, the data is encrypted with public keys whose corresponding private keys are only shared with B&A server instances running in TEEs and running public open-source binaries known to prevent cross-site identity joins (e.g. by preventing logging or other activities which might permit such joins). @@ -145,7 +206,7 @@ Another way to prevent the encrypted blob’s size from being a leak is to have 1. This would hugely complicate the B&A server’s interactions and API, making adoption likely infeasible. The B&A API would no longer be a RESTful API as it would have to coordinate communication from both the browser and other servers (e.g. contextual auction server). -1. This would also require the on-device JavaScript to determine whether to send the FLEDGE request to the B&A server, perhaps at a time before it has the results of the contextual auction which might influence the decision. Without this information the device would have to send the encrypted blob for every ad request, even in cases where the contextual call indicated it was wasteful to do so. +1. This would also require the on-device JavaScript to determine whether to send the Protected Audience request to the B&A server, perhaps at a time before it has the results of the contextual auction which might influence the decision. Without this information the device would have to send the encrypted blob for every ad request, even in cases where the contextual call indicated it was wasteful to do so. Exposing size of the blob is a temporary leak that we hope to mitigate in the future: diff --git a/FLEDGE_extended_PA_reporting.md b/FLEDGE_extended_PA_reporting.md index 52de9ac73..9b4b90d6c 100644 --- a/FLEDGE_extended_PA_reporting.md +++ b/FLEDGE_extended_PA_reporting.md @@ -181,8 +181,11 @@ The parameters consist of: * an `eventType` which is a string identifying the event type that triggers this report to be sent (see [Triggering reports](#triggering-reports) below), and * a `contribution` object which contains: - * a `bucket` which is a 128bit ID or a `signalBucket` which tells the browser how to calculate the bucket (represented as BigInt) and - * a `value` which is a non-negative integer or a `signalValue` which tells the browser how to calculate the value. + * a `bucket` which is a 128bit ID or a `signalBucket` which tells the browser how to calculate the bucket (represented as BigInt), + * a `value` which is a non-negative integer or a `signalValue` which tells the browser how to calculate the value, and + * a `filteringId` which is an optional integer in the range [0, 255] used to allow for separating aggregation service queries. For + more detail, please see the [flexible filtering + explainer](https://github.com/patcg-individual-drafts/private-aggregation-api/blob/main/flexible_filtering.md#proposal-filtering-id-in-the-encrypted-payload). Where `signalBucket` and `signalValue` is a dictionary which consists of: diff --git a/Fenced_Frames_Ads_Reporting.md b/Fenced_Frames_Ads_Reporting.md index 0668d80e2..740382052 100644 --- a/Fenced_Frames_Ads_Reporting.md +++ b/Fenced_Frames_Ads_Reporting.md @@ -52,7 +52,7 @@ There are two variants of the `reportEvent` API for event-level reporting that a This API is available from all documents in a fenced frame tree (i.e., the ad creative URL that won the Protected Audience auction). Child iframe or redirected documents that are same-origin to the mapped URL of the fenced frame config can call this API without any restrictions. Cross-origin child iframe documents can only call this API if there is opt-in from both the fenced frame root and the cross-origin document. The fenced frame root opts in by being served with a new `Allow-Cross-Origin-Event-Reporting` response header set to `true`. The cross-origin document opts in by calling `reportEvent` with `crossOriginExposed=true`. [See TURTLEDOVE issue #1077](https://github.com/WICG/turtledove/issues/1077) for the motivation behind cross-origin support. -The browser processes the beacon by sending an HTTP POST request, like the existing [navigator.sendBeacon](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon). +The browser processes the beacon by sending an HTTP POST request, like the existing [navigator.sendBeacon](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon). The POST request is sent immediately or as soon as the corresponding registerAdBeacon is invoked, and is done asynchronously to avoid blocking other actions. Note `window.fence` here is a new namespace for APIs that are only available from within a fenced frame. In the interim period when FLEDGE supports rendering the winning ad in an iframe, `window.fence` will also be available in such an iframe. @@ -330,7 +330,7 @@ For fenced frames rendering the ad components under the top-level ad fenced fram ``` window.fence.setReportEventDataForAutomaticBeacons({ - 'eventType': 'reserved.top_navigation', + 'eventType': 'reserved.top_navigation_commit', 'destination':['seller', 'buyer'] }); ``` diff --git a/PA_Feature_Detecting.md b/PA_Feature_Detecting.md index 50e62b0a0..0afb38a3a 100644 --- a/PA_Feature_Detecting.md +++ b/PA_Feature_Detecting.md @@ -85,7 +85,7 @@ const maxAdComponents = navigator.protectedAudience ? ``` ## Reporting timeout -[Intent to Ship](TBD) +[Intent to Ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/ZdZXN1D-MtI/) Inside `reportWin` one can determine its reporting timeout as follows: ``` @@ -101,8 +101,8 @@ const reportingTimeout = auctionConfig.reportingTimeout ? From the context of a web page, whether custom reporting timeout is enabled can be queried as follows: ``` -const reportingTimeoutEnabled = navigator.protectedAudience ? - navigator.protectedAudience.queryFeatureSupport("reportingTimeout") : false; +const reportingTimeoutEnabled = navigator.protectedAudience && + navigator.protectedAudience.queryFeatureSupport("reportingTimeout"); ``` ## Returning multiple bids from generateBid() @@ -121,7 +121,7 @@ the `targetNumAdComponents` and `numMandatoryAdComponents` bid fields will be considered. ## Cross-origin trusted signals -[Intent to Ship](TBD) +[Intent to Ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/5nvBAjmoO2g) From context of a web page: ``` @@ -129,6 +129,23 @@ navigator.protectedAudience && navigator.protectedAudience.queryFeatureSupport( "permitCrossOriginTrustedSignals") ``` +## Real time reporting +[Intent to Ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/9_dR-BdyeWE) + +From context of a web page: +``` +navigator.protectedAudience && navigator.protectedAudience.queryFeatureSupport( + "realTimeReporting") +``` + +## Selectable Reporting IDs + +From context of a web page: +``` +navigator.protectedAudience && navigator.protectedAudience.queryFeatureSupport( + "selectableReportingIds") +``` + ## Getting browser-side detectable features as an object Sometimes it's desirable to get status of all features detectable via `queryFeatureSupport` in a forward-compatible way. Sufficiently recent versions provide this functionality via @@ -153,7 +170,8 @@ An example return value would be: { "adComponentsLimit":40, "deprecatedRenderURLReplacements":false, - "reportingTimeout":true, - "permitCrossOriginTrustedSignals":true + "permitCrossOriginTrustedSignals":true, + "realTimeReporting":true, + "reportingTimeout":true } ``` diff --git a/PA_real_time_monitoring.md b/PA_real_time_monitoring.md index 3f2b3c902..04eb032e5 100644 --- a/PA_real_time_monitoring.md +++ b/PA_real_time_monitoring.md @@ -31,9 +31,9 @@ The goal of real-time reporting is to get auction monitoring data to the buyer a The high level Real Time Reporting API here is similar to the [Private Aggregation API](https://developers.google.com/privacy-sandbox/relevance/private-aggregation) in that it allows you to contribute to a histogram, but the contribution is subject to substantial local noise and is sent soon after the auction is completed to allow for a fast detection SLA. Once an error has been detected, [downsampled](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#712-downsampling) `forDebuggingOnly` can be utilized for root cause analysis. -This API will provide histogram contribution values of only 0s and 1s, which can be used to correspond to whether or not the event being monitored behaved expectedly or aberrantly. There are a number of events that adtechs may wish to monitor in real time - adtechs assign meanings to buckets for events in `generateBid()` and `scoreAd()` (e.g. an adtech might define bucket #44 as signal parsing error in generateBid). For certain event-types that cannot be reported from the aforementioned worklets, we propose the browser defines a set of platform-defined buckets (e.g. the browser might define bucket #12 as failure to fetch bidding or scoring script). Read more about [Platform Contributions here](#platform-contributions-reporting-errors-not-detectable-in-worklet-JS). +This API will provide histogram contribution values of only 0s and 1s, which can be used to correspond to whether or not the event being monitored behaved expectedly or aberrantly. There are a number of events that ad techs may wish to monitor in real time - ad techs assign meanings to buckets for events in `generateBid()` and `scoreAd()` (e.g. an ad tech might define bucket #44 as signal parsing error in generateBid). For certain event-types that cannot be reported from the aforementioned worklets, the browser defines a set of platform-defined buckets (e.g. the browser might define the first platform bucket as failure to fetch bidding or scoring script). Read more about [Platform Contributions here](#platform-contributions-reporting-errors-not-detectable-in-worklet-JS). -Note that the type of buckets that adtechs can define in `generateBid()` and `scoreAd()` can already be created in the Private Aggregation API. Where latency is acceptable, Private Aggregation API (with Aggregation Service) will provide more accuracy and should be the preferred solution for reporting beyond event-level `reportWin()` and `reportResult()`. +Note that the type of buckets that ad techs can define in `generateBid()` and `scoreAd()` can already be created in the Private Aggregation API. Where latency is acceptable, Private Aggregation API (with Aggregation Service) will provide more accuracy and should be the preferred solution for reporting beyond event-level `reportWin()` and `reportResult()`. ## Opting into reporting @@ -77,53 +77,112 @@ const BIDDING_SLOW_WEIGHT = 0.1; function generateBid(...) { // or scoreAd // Contribute to the histogram if the worklet execution takes longer than a // specified # of ms. - realTimeReporting.contributeOnWorkletLatency( - BIDDING_SLOW_BUCKET, - { priorityWeight: BIDDING_SLOW_BUCKET, + realTimeReporting.contributeToHistogram( + { bucket: BIDDING_SLOW_BUCKET, + priorityWeight: BIDDING_SLOW_WEIGHT, latencyThreshold: 500}); // In milliseconds - var bid; + let bidOut; try { // Bid generation logic... - bid = ... + bidOut = ... ... } catch (error) { - realTimeReporting.contributeToRealTimeHistogram( - BIDDING_THREW_BUCKET, - { priorityWeight: BIDDING_THREW_WEIGHT }); + realTimeReporting.contributeToHistogram( + { bucket: BIDDING_THREW_BUCKET, + priorityWeight: BIDDING_THREW_WEIGHT }); return; } - if (bid.bid > MAX_REASONABLE_BID) { - realTimeReporting.contributeToRealTimeHistogram( + if (bidOut.bid > MAX_REASONABLE_BID) { + realTimeReporting.contributeToHistogram( BID_TOO_HIGH_BUCKET, - { priorityWeight: BID_TOO_HIGH_WEIGHT }); + { bucket: BID_TOO_HIGH_BUCKET, + priorityWeight: BID_TOO_HIGH_WEIGHT }); return; } - return bid; + return bidOut; } ``` ## Platform contributions: reporting errors not detectable in worklet JS -There may be some errors that occur that are not visible in either `scoreAd()` or `generateBid()`. These include things like failures to fetch the bidding script, trusted real-time signals, or creative URL. In these cases, we propose to allocate a certain portion of the resulting histogram for platform errors. While this can be done as a completely separate set of buckets that are immutable from JavaScript, we would still require the number of total contributions across all buckets to be capped at 1, so these platform-assisted contributions will come with a default weighting and participate in the prioritization algorithm just like regular contributions. +There may be some errors that occur that are not visible in either `scoreAd()` or `generateBid()`. These include things like failures to fetch the bidding script, trusted real-time signals, or creative URL. In these cases, we allocate a separate set of buckets for platform errors. While this is done as a completely separate set of buckets that are immutable from JavaScript, we would still require the number of total contributions across all buckets to be capped at 1, so these platform-assisted contributions come with a default weighting of 1 and participate in the prioritization algorithm just like regular contributions. -The priority weight of platform contributions will be hardcoded and specified, so you can reason about the behavior across platform and developer contributions. +The priority weight of platform contributions is hardcoded as 1, so you can reason about the behavior across platform and developer contributions. +Platform contributions are reported in the report’s `platformHistogram` field, separately from regular contributions. See [below](#sending-reports-after-an-auction) for more details. + +| Bucket | Error | +| --- | --- | +| 0 | Bidding script fetch error | +| 1 | Scoring script fetch error | +| 2 | Trusted bidding signals fetch error | +| 3 | Trusted scoring signals fetch error | + +Fetch errors include non-2xx response code, response header does not have “Ad-Auction-Allowed: true”, response body cannot be parsed as valid json for trusted signals, etc,. ## Sending reports after an auction -After the auction completes, all opted-in participants (sellers, and buyers with interest groups present on device) will always emit one report per participant per auction, even if no ad wins or no `contributeToRealTimeHistogram()` calls are made. These reports will be sent to `domain.example/.well-known/interest-group/real-time-report` where `domain.example` would be replaced with the seller’s origin or the buyer’s interest group owner origin. These reports are not tied to specific interest groups, they are aggregated over all interest groups.The serialization format of the report is TBD, but see [below](#histogram-contributions-and-the-rappor-noise-algorithm) for the high level description of the encoding. Reporting domains may be configurable in the future. +After the auction completes, all opted-in participants (sellers, and buyers with interest groups present on device) will always emit one report per participant per auction, even if no ad wins or no `contributeToRealTimeHistogram()` calls are made. These reports will be sent to `domain.example/.well-known/interest-group/real-time-report` where `domain.example` would be replaced with the seller’s origin or the buyer’s interest group owner origin. These reports are not tied to specific interest groups, they are aggregated over all interest groups. See [below](#histogram-contributions-and-the-rappor-noise-algorithm) for the high level description of the encoding. Reporting domains may be configurable in the future. Participants who did not call `contributeToRealTimeHistogram()` will contribute an array of zeros by default, which will still require the input going through the noising mechanism to satisfy the privacy requirements. After the noise mechanism, it is very unlikely that output will be all zeros. Even still, if we encounter a contribution of all zeros post-noising, we will still report on it. This is important for debiasing the noisy results, as explained below. +Real time reports are sent as the body of an HTTP POST request to ad techs and are encoded as [CBOR](https://www.rfc-editor.org/rfc/rfc8949.html) with the following schema (specified using [JSON Schema](https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01)): + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "version": { + "type": "number", + "description": "Data version." }, + "histogram": { + "type": "object", + "properties": { + "buckets": { + "type": "byte string", + "description": "a byte string bit packed from regular contribution buckets, padded with zeros to the nearest byte." + }, + "length": "integer", + "description": "the number of regular contribution buckets before bit packing." + }, + }, + "platformHistogram": { + "type": "object", + "properties": { + "buckets": { + "type": "byte string", + "description": "a byte string bit packed from platform contribution buckets, padded with zeros to the nearest byte." + }, + "length": "integer", + "description": "the number of platform contribution buckets before bit packing." + }, + }, + "required": ["version", "histogram", "platformHistogram"] +} +``` + +Each histogram bucket value is encoded as a bit in a CBOR byte string ("buckets" byte strings in histogram and platformHistogram objects). Histogram bucket 0's value will be indicated by the most-significant bit in the first byte of the CBOR byte array. For example, [1,0,0,0,0,0,1,1, 1] will be packed to [0x83, 0x80] (or [131, 128]). See the "Data Notations" section of https://www.rfc-editor.org/rfc/rfc1700 for more information. + +Example: +```json +{ + "version": 1, + "histogram": {"buckets": [0x08, 0x09, …, 0x00, 0x84], "length": 1024}, + "platformHistogram": {"buckets": [0x90], "length": 4}, +} +``` + +In this example above, `histogram` field's buckets vector length is 128, and the number of buckets before bit packing is 1024. `platformHistogram`'s buckets vector is [1,0,0,1] (0x90 = 144 = 0b10010000) before bit packing. ## Histogram contributions and the RAPPOR noise algorithm -Each participant in the auction will maintain a histogram with a fixed number of buckets. Initially, we are proposing to fix the number of buckets to 1024, which was chosen both for system health reasons (bandwidth, etc), but also to discourage the measurement of fine-grained events, which are not easily compatible with strong local privacy protections. Callers that need fewer than 1024 buckets should just ignore the unused fraction of the histogram. +Each participant in the auction will maintain a histogram with a fixed number of buckets. Initially, the API fixes the number of buckets to 1028 (1024 regular contribution buckets and 4 platform contribution buckets), which was chosen both for system health reasons (bandwidth, etc), but also to discourage the measurement of fine-grained events, which are not easily compatible with strong local privacy protections. Callers that need fewer than 1028 buckets should just ignore the unused fraction of the histogram. Each auction participant can contribute a single time to this histogram from an auction. If an auction participant calls `contributeToRealTimeHistogram()` multiple times, one of those contributions is selected based on the `priorityWeight` param in the above API, and at the end of the auction a contribution is selected via weighted sampling. The `priorityWeight` param, required to be a [Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) greater than zero and less than infinity, dictates the relative likelihood of which bucket will get the contribution, so the priority for critical error types should be set significantly higher than other error types. For example, if an auction participant calls `contributeToRealTimeHistogram()` twice, once contributing to bucket 123 with a `priorityWeight` of 0.1 and once contributing to bucket 456 with a `priorityWeight` of 0.2, then their actual contribution from the auction has a $^1/_3$ chance of being in bucket 123 and a $^2/_3$ chance of being in bucket 456. @@ -135,10 +194,9 @@ buyer2.example: [0, 0, 0, 0, 0, ..., 0, 1, 0] buyer3.example: [0, 0, 0, 0, 0, ..., 0, 0, 0] buyer4.example: [1, 0, 0, 0, 0, ..., 0, 0, 0] seller.example: [0, 0, 0, 0, 0, ..., 0, 0, 1] - ``` -i.e. each auction participant holds a bit vector of length 1024, with at most one “1”. This encoding is important to describe the noise mechanism: RAPPOR ([Erlingsson et al 2014](https://research.google/pubs/rappor-randomized-aggregatable-privacy-preserving-ordinal-response/)). Basic RAPPOR noises each coordinate of the bit vector independently, and it is parameterized by `epsilon`, a measure of privacy loss. Here is an reference python implementation of the algorithm: +i.e. each auction participant holds a bit vector of length 1028, with at most one "1". This encoding is important to describe the noise mechanism: RAPPOR ([Erlingsson et al 2014](https://research.google/pubs/rappor-randomized-aggregatable-privacy-preserving-ordinal-response/)). Basic RAPPOR noises each coordinate of the bit vector independently, and it is parameterized by `epsilon`, a measure of privacy loss. Here is an reference python implementation of the algorithm: ```python import math @@ -160,9 +218,9 @@ def rappor(epsilon: float, value: int, histogram_length: int): return out ``` -In this proposal, we propose an epsilon value of 1 yielding a flipping probability of ~0.378 (value of f of ~.755). +For this API, we set an epsilon value of 1 yielding a flipping probability of ~0.378 (value of f of ~.755). -The very large flipping probability is the essential privacy protection of this proposal. In the report sent to the auction participant, probably around 370 to 400 of the 1024 buckets will be 1s. Moreover the actual bucket to which the auction contributed a 1 also has a 37.8% chance of its value being flipped, to a 0. +The very large flipping probability is the essential privacy protection of this proposal. In the report sent to the auction participant, probably around 370 to 400 of the 1028 buckets will be 1s. Moreover the actual bucket to which the auction contributed a 1 also has a 37.8% chance of its value being flipped, to a 0. This means that data returned from this mechanism should not be interpreted as-is, because it will be heavily biased from all the random flipping. Rather than interpreting directly, individual reports should be aggregated and then subsequently debiased with the following reference code: @@ -187,9 +245,9 @@ def debias_rappor(epsilon: float, N: int, histogram: Sequence[int]): The standard deviation ($\sigma$) from true count can be calculated using the formula, $\sigma \approx 2\sqrt{N}$, where N is the number of contributions. Measuring deviation at $2\sigma$ from true count will give the 95% confidence interval for errors. -This API’s primary goal is to help adtechs detect that errors are occurring and may need debugging. Due to the noisy contributions and probabilistic nature of the `priorityWeight` param, the count and frequency of these errors are not exact. For this reason, we recommend interpreting the output as a means to identify deviation from trends rather than representative counts for the frequency of contributions. Let’s consider an example: +This API’s primary goal is to help ad techs detect that errors are occurring and may need debugging. Due to the noisy contributions and probabilistic nature of the `priorityWeight` param, the count and frequency of these errors are not exact. For this reason, we recommend interpreting the output as a means to identify deviation from trends rather than representative counts for the frequency of contributions. Let’s consider an example: -Suppose an adtech receives 1M reports in 5 minutes, and Bucket 4’s count is 390,000. We can estimate the number of times an auction actually contributed to Bucket 4 using the formula (390000 - 1000000 * .755/2) / (1-.755), which gives us an estimate of approximately 51,000 contributions to Bucket 4 in this example. The standard deviation is approximately $2\sqrt{1M} = 2000$, so the 95% confidence interval is around 4000 on either side of the estimate. The adtech can be highly confident that of the 1M auctions that sent a report, the number that contributed their 1 to Bucket 4 is somewhere between 47,000 and 55,000, i.e. between 4.7% and 5.5% of the auctions. +Suppose an ad tech receives 1M reports in 5 minutes, and Bucket 4’s count is 390,000. We can estimate the number of times an auction actually contributed to Bucket 4 using the formula (390000 - 1000000 * .755/2) / (1-.755), which gives us an estimate of approximately 51,000 contributions to Bucket 4 in this example. The standard deviation is approximately $2\sqrt{1M} = 2000$, so the 95% confidence interval is around 4000 on either side of the estimate. The ad tech can be highly confident that of the 1M auctions that sent a report, the number that contributed their 1 to Bucket 4 is somewhere between 47,000 and 55,000, i.e. between 4.7% and 5.5% of the auctions. As the API will send reports soon after the auction is completed, the only delay in measuring aggregates comes from aggregating reports after the fact. The cadence and grouping of aggregation is entirely up to the API user, and due to the local noise any report can be queried or analyzed an unlimited number of times. For example, running the de-noising calculation on 1 minute's worth of reports, rather than waiting to collect for 5 minutes, will give estimates 4 minutes sooner, but at the cost of relatively more noise: the size of the 95% confidence interval will be approximately $\sqrt{5} \approx 2.2$ times as wide. @@ -225,11 +283,11 @@ Given that sellers have significant existing control over the auction, we deemed ## Privacy considerations -At the `epsilon` we are proposing ($\epsilon$ = 1), the entropy leaked is limited to approximately 0.18 bits per auction. This makes it very difficult for a bad actor to gain any meaningful user identifying information from an auction using this API. +At the `epsilon` we are proposing ($\epsilon$ = 1), the information leaked is limited to approximately 0.18 bits per auction[^1]. This makes it very difficult for a bad actor to gain any meaningful user identifying information from an auction using this API. While the tight privacy parameters provide strong protections, there are two privacy considerations of note: -* It reveals a small amount of information from scoreAd and generateBid to sellers and bidders, respectively. These contents are protected by the locally differentially private RAPPOR algorithm. The scope of this risk can be measured with the privacy loss epsilon parameter. This risk could be magnified by a bad actor running many auctions solely for the purpose of collecting more information from a publisher page. At launch, we plan to mitigate this risk by bounding the number of contributions that this API will send to an adtech from a page in a given period of time for each browser. +* It reveals a small amount of information from scoreAd and generateBid to sellers and bidders, respectively. These contents are protected by the locally differentially private RAPPOR algorithm. The scope of this risk can be measured with the privacy loss epsilon parameter. This risk could be magnified by a bad actor running many auctions solely for the purpose of collecting more information from a publisher page. We mitigate this risk by bounding the number of contributions that this API will send to an ad tech from a page in a given period of time for each browser. It’s set to 10 reports per reporting origin per page per 20 seconds (per browser). * It reveals to the ad tech the fact that it had an interest group present on the device. This is mitigated by the fact that the reports do not contain any information about which auction triggered them, and the report is heavily noised. We also considered sending reports to all *eligible* auction participants for a given auction (i.e. all those present in `interestGroupBuyers`, even if they do not have interest groups), but this will result in an overwhelming number of reports sent. We plan to address both these considerations in [future work](#limitations-and-future-work). @@ -253,14 +311,14 @@ The downsampled reporting is better suited for root-cause analysis. We could consider extending the [Private Aggregation API](https://developers.google.com/privacy-sandbox/relevance/private-aggregation) rather than introducing a new API surface for real-time bid reporting. There were a few reasons we chose a new API: -1. Due to the random client-side delay, adtech time to accumulate reports and batch processing time, PAA reporting SLA may not be brought down to the real time nature of reporting for monitoring required by some adtechs. Reducing the delay is possible but would likely require sending more null reports to satisfy the privacy requirements. +1. Due to the random client-side delay, ad tech time to accumulate reports and batch processing time, PAA reporting SLA may not be brought down to the real time nature of reporting for monitoring required by some ad techs. Reducing the delay is possible but would likely require sending more null reports to satisfy the privacy requirements. 1. PAA is designed for a very large domain of keys in its underlying histogram, whereas the RAPPOR mechanism cannot readily support such large domains due to bandwidth / computation constraints. 1. Utilizing PAA could interfere with existing PAA privacy budgets, and it isn’t clear exactly how to share budgets across central and local mechanisms. ## Limitations and future work -The proposal suggested in this document is limited in scope due to the strong privacy guarantee and use of local noise. In the future, we plan on exploring alternative architectures such as shufflers or trusted servers to improve utility without compromising on privacy or the real-time requirement. In particular, we are exploring two solutions with the following goals: +The API is limited in scope due to the strong privacy guarantee and use of local noise. In the future, we plan on exploring alternative architectures such as shufflers or trusted servers to improve utility without compromising on privacy or the real-time requirement. In particular, we are exploring two solutions with the following goals: * Decrease the amount of total noise * Allow for measuring larger and more fine-grained histograms @@ -268,3 +326,5 @@ The proposal suggested in this document is limited in scope due to the strong pr * Address the privacy concern that this proposal leaks the auction participation The two solutions we are considering are 1) add a local shuffle and IP address proxy mechanism to the Real Time Monitoring API, or 2) replace the Real Time Monitoring API with a low-latency Private Aggregation solution. + +[^1]: Wang, Shaowei et al. “Mutual Information Optimally Local Private Discrete Distribution Estimation.” https://arxiv.org/abs/1607.08025 (2016): Theorem 2.1 diff --git a/meetings/2024-07-10-FLEDGE-call-minutes.md b/meetings/2024-07-10-FLEDGE-call-minutes.md new file mode 100644 index 000000000..50c27abc5 --- /dev/null +++ b/meetings/2024-07-10-FLEDGE-call-minutes.md @@ -0,0 +1,235 @@ +# 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 10, 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. Brian May (Dstillery) +4. Matt Menke (Google Chrome) +5. David Dabbs (Epsilon) +6. Matt Kendall (Index Exchange) +7. Sven May (Google Privacy Sandbox) +8. Orr Bernstein (Google Privacy Sandbox) +9. Brian Schmidt (OpenX) +10. Harshad Mane (PubMatic) +11. Ricardo Bentin (Media.net) +12. Fabian Höring (Criteo) +13. Arthur Coleman (IDPrivacy) +14. Laurentiu Badea (OpenX) +15. Kevin Lee (Google Privacy Sandbox) +16. Matt Davies (Bidswitch | Criteo) +17. Victor Pena (Google) +18. David Tam (Relay42) +19. Owen Ridolfi (Flashtalking) +20. Laura Morinigo (Samsung) +21. Jeremy Bao (Google Privacy Sandbox) +22. Paul Spadaccini (Flashtalking) +23. Omri Ariav (Taboola) +24. Alonso Velasquez (Google Privacy Sandbox) +25. Neha Gautam (Carnegie Mellon) +26. Becky Hatley (Flashtalking) +27. Kenneth Kharma (OpenX) +28. Aymeric Le Corre (Lucead) +29. Andrew Pascoe (NextRoll) +30. Abishai Gray (Google Privacy Sandbox) +31. Maybelline Boon (Google Privacy Sandbox) +32. Alex Peckham (Flashtalking) +33. Sid Sahoo (Google Chrome) + + +## Note taker: David Tam (or when he's talking: Arthur Coleman) + + +# 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 +* Jeremy: + * Seek feedback on Negative IG solution and usage budget +https://github.com/WICG/turtledove/issues/896 + +* David Tam: + * Seek feedback on proposal for how seller can set interest groups for buyers to bid on - https://github.com/WICG/turtledove/issues/1196 +* Matt Davies: + * Seek feedback on third party reporting + + https://github.com/WICG/turtledove/issues/1220 + + + +# 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 + + +## JeremyBao: Seek feedback on Negative IG solution and usage budget, https://github.com/WICG/turtledove/issues/896 + +Jeremy Bao - product manager working on PAAPI with Privacy Sandbox. Seeking feedback on negative IG proposal. Supports IG targeting on contextual bids, can support negative targeting in PA bid. Add a field in IG. Must have the same owner. When the auction is initiated then the negative IG is filtered out. For negative IG set maximum 3 per IG, for contextual the maximum is 10. + +David Dabbs - Great. I wanted to bring your attention to Fabrice's question about needing additional bid key in PA bids + +Orr Bernstein: Don’t need additional bid key in negative IG. + +Additional bid keys are needed for additional bids. + +David Dabbs - Potential expansion of this feature. Is it possible to have negative targeting for a single positive IG. Don’t participate if the user is in a positive IG. + +Jeremey Bao - I assume the solution will solve this. + +David Dabbs - Is it beyond the pale of privacy protection. + +Jeremey Bao - Negative IG are tiny. Is there any reason why you just do not use negative IG. + +Michael - Positive and negative roles are separate from each other. It seems wise to keep them that way. I don't want to deal with questions of what happens when positive IG A is filtered out because of a negative IG B but is also being used as a negative IG by positive IG C. + +Brian May - What is the implication for k-anon. + +Michael - None at all. K-anon is not based on who is eligible to participate in an auction, only on winners of an auction. If an IG does not bid in an auction then it has nothing to do with k-anon- If you are not a potential winner of an auction then there is no k-anon. Only candidates that won an auction. + +Omri Ariav - adding negative targeting to the paapi flow can help in the negative IG native advertising use case. How to follow these announcements. + +David Dabbs - Watching the repo and the issues. + +Omri Ariav - Suppression or negative IG in PA flow. In terms of timeline what is the expectation of providing feedback. + +Jeremey Bao - we are still working on that piece. Timeline is not finalised. + +Michael - There are many github issues around the same set of questions. #1072, #1092 are similar issues. The negative IG work is also relevant. #1199 is more about interactions of ads on the same page. + +David Dabbs - Negative IG has to have the additional bid key. + +Michael - To remove from the auction contextually targeted ads. Extending the negative targeting capability it can also apply to bids from other IGs. + +David Dabbs - There was a negative IG could not have an updateURL. Requesting that the scope of the lightweight creation of IGs via HTTP headers encompass this new negative targeting use case. A neg IG is a lightweight IG use case. + +Jeremy Bao - Don’t know much about header joining. Need to explore more. Negative IG still additional bid keys. IG may still need to make additional bids. Additional bid key is part of a negative IG, though not needed for this new PA bids usage. + +Orr Beinstein - You should set an additional bid key in your negative IG, that way you can use it in both ways, contextual and other-IG. + +Michael - Even if you intend to use negative IG exclusively for negative targeting from other IGs, and you're sure that additional bids are irrelevant, the additionalBidKey is still needed to make the negative IG work. That's how we recognize a negative IG in the first place. + +Orr - It still needs to be there. + +David Dabbs - Not proposing that neg IGs be updatable. As I recall you have a validity check on addlKey (so passing empty string or even some non-key token value) probably not work. + +Orr - Join negative IG instead of join IG. + +Michael - Let negative IG filter out positive IG. Additional bid is a required field. + +David Dabbs - Over time the special-case single value `negativeInterestGroup` attribute would be unneeded and be deprecated (since you are eliminating the same-origin restriction). + + +Orr - Yes, we can simplify things. + + +Jeremy Bao - Agree that you do not need that special thing. An attribute maybe. No need to read origin domain. + + +## David Tam: Seek feedback on proposal for how seller can set interest groups for buyers to bid on - https://github.com/WICG/turtledove/issues/1196 + +[David Tam] Came up with a proposal on how publishers could create interest groups. Idea is that we add additional attributes to the IG to signify that it is a seller defined interest group. Based on that we can populate the dynamic attributes. Want to know if that is a possibility. + +[Michael] Looking at your proposal and who is here. No one from RTB House seems to be here. Their prime audience product is a seller-based audience launched on top of Protected Audiences - so it sounds like they are doing what you are proposing. This is a use case we have discussed in the past. + +[David] RIght now only the buyer can create an IG. But according to the specification there are three parties involved. The advertiser, the tech vendor that an advertiser can delegate to, and the publisher. So when a publisher creates an interest group, how does a buyer buy that interest group? The only way that is monetized today is that the advertiser or the delegated tech vendor of the advertiser creates it. It implies that all the attributes of that audience are known at creation time of the renderURL which, if this were a publisher-created interest group, you would not know in advance if you don’t know who the buyer is. + +[Michael] An interest group inherently has to contain some bidding logic since the interest group is the element that produces bids. If you want to use a model in which a publisher wants to participate in the creation of an audience, people who visit their site, then the model we have discussed before is one in which the publisher is going to need to work with some ad tech or multiple adtechs involved in the audience use business. So you are quite right that at some point there needs to be a delegation from the publisher that is involved in audience creation to the person who is actually doing the bidding. In the Protected Audience model we have today, that happens at the time of interest group creation. So if a publisher wants to create an interest group and make it available to different buyers, we recommend they have a set of different interest groups, one for each buyer they are offering these interest groups to. We are not set up to have anybody build one interest group that holds onto different pieces of bidding logic that come from multiple different ad techs. We put everything together by having a bunch of different interest groups - one each for each of the potential buyers. + +Second, for what you asked about the ad creatives not known in the browser ahead of time but only something you learn as a response in the middle of the auction as a response from the key value server: It is a problem because it does not play well with the k-anonymity constraints. The way that a browser knows that an ad is k-anonymous and therefore knows that it has appropriate privacy properties to show to a user, involves looking information up on the k-anon server which is an operation that generally happens earlier in the process, not in the critical path. So it is not viable from the latency point of view for the browser to only learn what the candidate ads are after all the bidding, key value return and and then Javascript execution of the bidding has taken place. That is late in the process. A lot of other issues - poor performance properties and poor privacy properties, for example. There is a issue in Github repository by Martin Thompson that points out the kind of privacy leakage that you could exploit if we made the change you're asking for. + +[David] So in the context, how would a publisher create an interest group to sell to different buyers that want to bid for it? + +[Michael] Exactly the reason I am talking about this. I think it is extremely reasonable for anyone to create and monetize an audience. But the transaction in which there is some kind of deal between the audience creator and buyer of that audience is not something that happens in the moment of the auction. Like many parts of PAAPI, things should happen ahead-of-time even though in ORTB we think about them happening at auction time. With PA we think of them happening earlier at interest group creation time or update time, for example. This is a good example of that. The audience creators' choice of what buyers they want to work with given an opportunity to buy this audience in exchange for dollars is something we feel should take place ahead of time and if that requires some new sort of reporting for this audience at bidding time, then we are interested in providing additional reporting functions. A lot of discussion on this topic has happened in this group. We certainly can talk about shortcomings. But handing an audience from an audience creator to a buyer in the Protected Audiences world has to happen ahead of time, not in the moment of the auction. + +[Daivd] Does that mean you are effectively agreeing that in some kind of offline world, that you want to buy this audience. So the publisher or whoever they delegate to create the audience will sell it while the creative… that all of that is basically agreed to in advance before that interest group is created? + +[Michael] I think that the sort of thing you are talking about could happen in an automated real-time way at the time of audience creation. Most things that involve financial transactions between two people tend to need some kind of setup to happen ahead of time outside the browser, of course, because it involves money changing hands. So I don’t think that is something dramatically new and different. Aside from the issue of how the payment channel gets established, the idea of creating a new audience and making it available to multiple parties to bid on is absolutely something that can happen in real time between two parties that have agreed on audience creation and then audience delegation based on whatever protocol and financial consideration they want. Again, I'm sorry the RTB House folks aren't here because they have worked on this and I expect they would have interesting opinions to contribute. Maybe we can revisit this on a future call when folks with more experience are here. + +[David] Last question, in the case of the publisher creating the interest group, who initiates the auction? + +[Michael] Exactly as they are today. Initiated on a publisher page by a publisher or ad tech that publisher works with. + +[David] The interest group will basically have the owner initiate some buyer. And then the bid requests will go to that buyer. Correct? + +[Michael] Yes that is the way it works today in Protected Audiences. + +[David] Do you have a reference to the RTB product? + +[Alonso] Yes, Prime Audience. [www.primeaudience.com](www.rimeaudience.com) + +[Brian] Wavering back and forth because I’m not sure how far into this we want to get. I assume the concern for publishers is that I want to maintain control over the value that I provide to my buyers via my audiences. We may want some way of metering the value that I am providing a buyer so that I know what my interest groups are. Are some interest groups more valuable on one browser versus another? And if someone is doing something I am uncomfortable with, can I block them from using those audiences? Publishers want to abide by a set of rules, and those are maintained by the goodwill of those who are using them versus any control you have directly over things. + +[Michael] That makes a lot of sense. Offhand I can think of three plausible approaches to deal with that set of questions: + + + +1. Some contractual arrangement between audience creator and audience buyer on what those rules are and they enforce the contract however they want. +2. There is some mechanism in the browser that allows for _reporting,_ so that the party that was involved in audience creation can get a stream of information about how the audience later gets used. That lets them monitor proper usage vs.the contract. That is a monitoring approach wiIth the possibility for after-the-fact repercussions if someone is acting in the wrong way - after the fact reporting. +3. There is some mechanism in the browser that allows for _control,_ so that the use of interest groups is not solely in the hands of the buyer. Instead there are other controls - multiple parties involved in real-time that ensure that the interest group is used in the way it was supposed to - buyer and seller have independent control over what the interest group does at the moment at the auction. + +Seems to me like 3 is rather difficult - not sure if it's impossible, but at least relatively difficult and different than how Protected Audience mechanics work today. On the other hand 2 - seems very compatible with the way Protected Audiences works today. This may be fully supportable today with our additional reporting endpoints, or we may need to add some additional reporting so that parties involved in audience creation can be sure they have an ongoing ability to monitor how the audiences are used. If there are holes in reporting that make it not suitable for this use case, then we can talk about adding additional reporting features. + +[Brian] I think for the second option I would want to be able to kill my interest groups as well. If someone doesn’t abide by contract, shut them down and make them non-functional + +[Michael] I understand the desire for a kill switch, but I’d have to think about how to implement that. Doesn’t seem like something that naturally exists today. Less demanding than full control, kind of an option 2½ . So I would need to think about it. + +[Brian] Maybe KV server could allow the owner to do that + +[Michael] The KV server touching the browser at the time of the auction is in control of the buyer, so the wrong party in the trust model you're worrying about. I will keep thinking about this. It is an entirely reasonable use case and one we are happy to talk about supporting. If features are missing, we can certainly find them. + +[Roni] Brian captured a lot of it. A difference between “this is my audience and you can use it” and “I am creating an interest group on your behalf” - which is what the browser lets you use today. Sure it can be done post- auction - but that is a bit problematic. Sure reporting should tell you what happened but there should be better responsiveness. There are then scalability challenges. Also the issue of making 100s of interest groups for every buyer they have seems “heavy” and problematic. In the interest of time, I will leave it there but want to noodle more. + +[Michael] Yes, interesting things for us to think about. + +[Matt Davies] In generic terms, what about third parties that want to create an interest group? Maybe we give it to an agency, who then might want to have multiple options for DSPs to do that. Does it just have to be at time of creation or could there be other use cases? Where a data provider can create the interest group and then have it duplicated into multiple SSPs/DSP? + +[Michael] Right now it has to be done at creation time. Willing to think about your use case, but am concerned about cloning an interest group. That sounds like a way to get runaway interest group creation. Something we need to pay attention to. Figuring out how to defer…how to allow a third party to take the interest groups or to allow them to default to them. You can’t have a set of different bidders each running their own code. Not interested to have every interest group having an auction that feeds into itself. Not interested in creating taller and deeper trees of IGs at auction time. + +[David] Agree with Roni. Does not make sense for publishers to create hundreds of thousands of interest groups. And if only one buyer can actually bid on an interest group then why is there a need for PA? The aim is for the seller (publisher) to create an interest group and auction this interest group to a number of buyers. + +[Michael] Are we talking about a buyer bidding on an interest group created by a publisher when visiting that same publisher's site, or when they leave the publisher site and see an ad elsewhere? If directly on their site, you don’t need Protected Audiences at all! That can just be done with information passed between the publisher and the buyer, and it doesn’t touch any of these APIs at all. Protected Audiences only applies if you want to create an audience on site 1 and use it on site 2. If you're using it on site 1 as well, for example a "Seller Defined Audiences" use case, then this whole discussion is not relevant. + +[Brian] So agree with Roni about the explosion of interest groups to gain a foothold on browsers. We need to find a way to prioritize interest groups. + +[Michael] It is what we already do. + +[Brian] What I’m concerned about is you hit a publisher page, it creates a bunch of interest groups, it washes out a number of interest groups - and we end up constantly churning through interest groups. + +[Michael] Excellent reason that interest group creation should be in the hands of buyers who are actually using the interest group. diff --git a/meetings/2024-07-17-FLEDGE-call-minutes.md b/meetings/2024-07-17-FLEDGE-call-minutes.md new file mode 100644 index 000000000..29e616d9c --- /dev/null +++ b/meetings/2024-07-17-FLEDGE-call-minutes.md @@ -0,0 +1,224 @@ +# 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 17, 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. Tomer Ben David (Taboola) +4. Sven May (Google Privacy Sandbox) +5. Roni Gordon (Index Exchange) +6. Andrew Pascoe (NextRoll) +7. Fabian Höring (Criteo) +8. Russ Hamilton (Google Privacy Sandbox) +9. Paul Jensen (Google Privacy Sandbox) +10. Sathish Manickam (Google Privacy Sandbox) +11. Laura Morinigo (Samsung) +12. Orr Bernstein (Google Privacy Sandbox) +13. Matt Menke (Google Chrome) +14. Harshad Mane (PubMatic) +15. Laurentiu Badea (OpenX) +16. Ricardo Bentin (Media.net) +17. Alex Peckham (Flashtalking) +18. Owen Ridolfi (Flashtalking) +19. Rickey Davis (Flashtalking) +20. Becky Hatley (Flashtalking) +21. Tim Taylor (Flashtalking) +22. Arthur Coleman (IDPrivacy) +23. Matt Kendall (Index Exchange) +24. Matt Davies (Bidswitch | Criteo) +25. Tamara Yaeger (BidSwitch) +26. Isaac Foster (MSFT Ads) +27. Warren Fernandes(Media.net) +28. Anthony Yam (Flashtalking) +29. Kevin Lee (Google Privacy Sandbox) +30. Jeremy Bao (Google Privacy Sandbox) +31. Achim Schlosser (Bertelsmann) +32. Luckey Harpley (Remerge) +33. Koji Ota(CyberAgent) +34. Alonso Velasquez (Google Privacy Sandbox) +35. Antoine Niek (Optable) +36. Denvinn Magsino (Magnite) +37. Abishai Gray (Google Privacy Sandbox) +38. David Dabbs (Epsilon) +39. Felipe Gutierrez (MSFT Ads) +40. Ken Gordon (Azure) +41. Jacob Goldman (Google Ad Manager) +42. Kenneth Kharma (OpenX) +43. Shafir Uddin (Raptive) +44. Maybelline Boon (Google Privacy Sandbox) +45. Laszlo Szoboszlai (Audigent) +46. Hari Krishna Bikmal (Google) + + +## Note taker: Tamara Yaeger + + +# Agenda + + +## Process reminder: Join WICG + +If you want to participate in the call, please make sure you join the WICG: https://www.w3.org/community/wicg/ + +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 + +* Matt Davies: + * Seek feedback on third party reporting + + https://github.com/WICG/turtledove/issues/1220 + + +* 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 + * 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) nNegative 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 negatively-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. + + + +# 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/meetings/2024-08-21-FLEDGE-call-minutes.md b/meetings/2024-08-21-FLEDGE-call-minutes.md new file mode 100644 index 000000000..41a7f9a77 --- /dev/null +++ b/meetings/2024-08-21-FLEDGE-call-minutes.md @@ -0,0 +1,305 @@ +# 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 21, 2024 + + +# NO MEETING AUGUST 14 +due to a lack of agenda items, Happy Summer + +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. Patrick McCann (Raptive) +3. Brian May (Dstillery) +4. David Tam (https://paapi.ai ) +5. Matt Davies (Bidswitch | Criteo) +6. Ivan Staritskii (Bidswitch | Criteo) +7. Arthur Coleman (IDPrivacy) +8. Laurentiu Badea (OpenX) +9. Brian Schmidt (OpenX) +10. Alex Cone (Google Privacy Sandbox) +11. Anthony Yam (Flashtalking) +12. Becky Hatley (Flashtalking) +13. Sven May (Google Privacy Sandbox) +14. Paul Jensen (Google Privacy Sandbox) +15. Sathish Manickam (Google Privacy Sandbox) +16. Jeremy Bao (Google Privacy Sandbox) +17. Tomer Ben David (Taboola) +18. David Dabbs (Epsilon) +19. Matt Menke (Google Chrome) +20. Isaac Foster (MSFT Ads) +21. Tamara Yaeger (BidSwitch) +22. Abishai Gray (Google Privacy Sandbox) +23. Shafir Uddin (Raptive) +24. Bharat Rathi ( Google, Privacy sandbox) +25. Koji Ota(CyberAgent) +26. Courtney Johnson (Privacy Sandbox) +27. Paul Spadaccini (Flashtalking) +28. Kenneth Kharma (OpenX) +29. Andrew Pascoe (NextRoll) + + +## Note taker: Tamara Yaeger + + +# Agenda + + +## Process reminder: Join WICG + +If you want to participate in the call, please make sure you join the WICG: https://www.w3.org/community/wicg/ + +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 (meetings with no items may be canceled) + + + +* [David Tam] I would like to return to this issue - https://github.com/WICG/turtledove/issues/1196 and how we can come up with a solution that does not require Publishers to delegate IG creation to a DSP. Instead enable Publishers to curate and sell their IG in an open auction. +* [David Tam] Add seller information to the KV server request - https://github.com/WICG/turtledove/issues/1249 +* [Matt Davies] Follow up to https://github.com/WICG/turtledove/issues/1220 to understand the next steps in getting reporting for third parties outside of the component auction but within the billing chain + + + +# 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 + +Michael (Chrome): + +W3C's annual meeting TPAC is coming up, https://www.w3.org/2024/09/TPAC/, third week of Sept, Chrome will ask for MSFT to have a joint session to discuss convergence between PAA and Ad Selection API. Great job of converging previous 2 years. Might be Tuesday or Thursday that week, subject to schedule availability. Remote attendees will be welcome, but will use different infrastructure for TPAC than for our normal weekly calls. + +Upcoming meetings – Michael Kleber unavailable next week (Aug 28); will decide whether to keep it at end of this call by show of hands. + +ANSWER: Meeting as usual next week, Paul will direct while Michael is away ("See you in September!") + +No automated note-taking, has not been approved by W3C :] + + +## [Issue 1196](https://github.com/WICG/turtledove/issues/1196): Solution that does not require pubs to delegate IG creation to DSP. + +David Tam () + +Long-standing issue, want to find out where we got to. + +David Dabbs (Epsilon) + +I think we got hung up on creation vs ownership. Do you want the pub to execute as owner? Anyone can delegate creation, but not anyone can own it. You really want the pub to own it. + +David Tam + +Yes + +Michael +I think ownership has some problems. There is a field in IG that has the name ‘owner’ is my unfortunate choice from back in 2020. I’m in favor of separating out diff roles and letting diff parties do diff roles. Would prefer to avoid saying “ownership” and being more clear on the roles, and when they happen, and how they are delegated. + +Brian May (Dstillery) + +I like the def of ownership to understand what kind of control is exercised over IG. + +Michael Kleber (Chrome) + +We had some discussion of diff roles, back in the July 10th call ([notes](https://github.com/WICG/turtledove/blob/main/meetings/2024-07-10-FLEDGE-call-minutes.md#david-tam-seek-feedback-on-proposal-for-how-seller-can-set-interest-groups-for-buyers-to-bid-on---httpsgithubcomwicgturtledoveissues1196)). We can pick up from there. The thing that I call “owner” is the thing we should describe as the person who does the bidding. An IG is something that bids, and it holds some JS bidding logic and the info used at the moment of auction to combine data to produce bids; when I imagined IGs, the “owner” would decide how bidding worked. Could be “party responsible for bid” + +David Dabbs + +Anyone can place the initial content of IG, but only the “owner/bidder” update it (in addition to bidding). Perhaps that’s what MK meant by other roles. + +Patrick (Raptive) + +I heard a lot of parties and in supply side circles, particularly entities who don’t bid but have intimate relationships with their audiences. It seems the solutions have pointed to additional output gates, but a lot of entities are on the sidelines waiting for fundamental architecture change. Party doing bidding + party setting storage on browser exist of this user; if that has to be the same party, that should be clearly communicated to the supply side. + +Michael + +Those 2 entities can be different, and the mechanism has existed as long as TURTLEDOVE / FLEDGE proposals. There is an entity that does bidding, “owner” for now, and the creation of the IG does not need to be done by that party. They can delegate who creates audiences used for bidding later on. The person doing the bidding wouldn’t expect anyone to allow this, especially malicious parties, but they can delegate the ability to make audiences to whoever they choose. There is already a mechanism that exists with permission files and special locations. If the bidding party wants to [delegate the permission to create audiences](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#13-permission-delegation) that they use later, it can happen. + +David Tam + +How the IG is set, you can’t separate bidding from IG, so you have to do both at the same time. My question is maybe K-Anon issue, but can we separate that out, w/out having to say they are the bidder? + +The pub can create the IG, and what I’d like to see is to say to every buyer is that the IG and they’re open to bid, and advertiser / DSP provide bidding logic as buyer. + +Michael + +This is indeed possible today! Could be pub who is IG creator or could be any other party in that role, let's call them something like the "audience identifier", some kind of party that is expert in saying X person belongs in Y audience. I’m an expert and can recognize that, that’s one role. That role can say they saw something happened on a website (whether they are a pub or 3P), and can figure out audiences, potential bidders, etc. The mechanism in PA is: at the moment of audience identification, the audience identifier party can off-the-browser say to everyone who likes bidding on their audiences, "we identify this person can be in this audience, and I can create an IG to let you bid on this audience". This could happen in a server-to-server convo between audience creator or future bidder, or in browser where IG creator creates various iFrames, server-to-server calls to potential bidders. If multiple bidders want to take that audience creator up on the offer, then those bidders can use different interest groups to bid on that same person, because each has different bidding logic for IGs. Independent JS do independent things, but the various bidder IGs can all be created at the same time by a party that can identify person that can be in each of those audiences. + +Brian + +The interest is not in being able to create IG, but to create IG for best return and control relationship w person as it relates to IG + +Michael +Selling this IG to who will benefit the most happens at the moment of IG creation, not in IG use in the middle of auction. + +Brian +I wish I could create an IG, sell it for some days, to another for other days, and purge bidding logic in between. + +Michael +Selling an IG for a period of time is feasible right now. Having an IG start later for an unknown buyer does not work, no way to do that today. + +David Tam + +I would like to see that the pub can say here’s my IG, maybe 5-6 number of bidders, and they bid dynamically. The pub sets IG, and says to all the buyers, they have this particular audience I can sell. They can be any number of buyers interested in buying that audience. They can all submit that bid, today the only exception is that buyer puts that in there… + +Isaac (MSFT Ads) + +I’m just curious, maybe abstractly we can say these are objects owned by entities w actions that can be performed. There exist diff kinds of access control options. Sounds like we’re talking about an action we’d like to delegate in a more subtle way. How would you start to plan a framework in that direction where the initial creator can set privileges. + +Michael + +That’s a direction I would be happy to go, if we have a clear understanding of delegations and rights. For performance reasons, browsers will have a strong preference of _not_ doing delegation based on a new round of delegation logic that runs during the moment of auction. It’d be better not to happen during those precious millisecs. + +Isaac + +Hypothetically if I create IG, I can grant bidding privilege; maybe I can do something specific per IG. Would that help what Brian was talking about; I do agree having to dupe objects to do delegation will result in sadness. I’m not sure of the run time use case; whether this direction would help or not. + +Michael + +That’s in line with my thinking, just nailing down description on what we want clearer would be useful. + +Isaac + +Do you agree that even absent of knowing a certain privilege, sounds like we’re moving in the more resources-actor privilege? + +Michael + +Interested in exploring, adds complexity to API implementation, need cost analysis. + +Brian +Want to suggest exploring this model w/out tying it to client side implementation of PAAPI. Server side may be able to do this with appropriate capabilities. Prior to auction can be done on server side relatively easily, then present to server side final auction what should be represented. + +Isaac + +Doubt anyone disagrees, maybe the connection to client would be that the client needs to make decision what data and decryption keys will be released. Important to align w these guys because if we build the best feature on server side, but insecure, it doesn’t matter. + +David Dabbs + +The agent there is the owner-bidder in either case? + +Isaac + +Can’t get away from something creating a thing, but it’s nice to be able to delegate an action to other entities. Could be more object management friendly, it would also open up the possibility of additional things in the future, have to start somewhere. + +Jeremy (Google Privacy Sandbox) +Can you describe more of your biz case? What parties are involved? + +David Tam + +At the moment looking at retargeting, primary way of using protected audience. If we can open pub audiences it will give us bigger reach so we can target across funnel; targeting what appears on pub’s website not just advertiser’s. Gives more audiences, we can drive more performance for advertisers. Advertisers who want to reach new customers, for example, they’ve been asking for this because in a cookie-free environment this would be the best vehicle for retargeting campaigns. + +Michael + +That’s a great use case, can you explain why that use case is not addressed by handing IG from creator to bidder at time of creation? Brian just identified a use case about serially using IG groups, is that what is missing? + +David Tam + +You’re negotiating a deal beforehand, but I’m saying why not let the auction decide who the best bidder is? + +Michael + +You can absolutely use an auction, at time of IG creation, if you want. If you choose to accept 5 diff bidders for IG audience, then you can create 5 diff interest groups. They can bid in every auction and might have opp to add to that audience. + +David Tam + +Say a pub has an audience they want to sell to a variety of diff buyers, how would they say they have an audience who wants to place a bid on it? How does the mechanism work? Would the seller have to tell all the buyers, not w/in the browser, that they have the audience. So what I would like to see is for it to happen in the browser. + +Michael + +You can, you can write your own JS that does it in the browser; call all the other ad techs you hope to bid, announce the opp, and ask if they want to create the audience or not. Or you could do it server-to-server outside the browser, either way works. + +David Tam + +The seller has to write the JS? + +Michael +Yes, the party that wants to sell the audience, at the time you recognize you want to put a person in the audience. IG event can be sold to as many people are interested in buying at moment of IG creation. Up to your biz negotiations. + +Jeremy + +Let’s say I built an audience 3 mos ago, what if I have a new buyer? + +Michael +Handing an audience to another person happens at audience creation; you can’t put pre-existing people into an audience for a new bidder later, unless you see that person again. That is right and closely related to Brian’s point. We could add a new mechanism to support that, but that sort of delayed-reaction audience assignment is not possible in PA today. But if what is supported is adding someone recognized to audience event being sold, all of whom will be able to buy and target that audience in the future, that already works. + +Brian +Who can access IG and for how long reflects deficiency in ad tech, if a cookie is allowed to be set, it can be found on many other sites. One of the things that PA and IG ownership in PA can do is preserve the value that pub can develop audiences they have by controlling means of access to those IGs. + +Michael + +Back in July, we talked about: what sort of joint control can be exercised by a combo of IG bidder and the audience creator? What sort of control should be retained when bidding ability is given away? We talked about contractual agreements and reporting, so that the audience creator can find out how the audience was used, and can monitor any breaches of agreement. If after the fact reporting is not good enough, and IG creators want to retain some amount of control of when / where / how IG gets used, let’s talk about scope and implementation w performance characteristics. + +David Dabbs + +(Addressing David Tam) Do you need this to utilize PA? Your description is echoing Michael’s seller defined audience and having many people bid on it, with the caveat that they can monetize that wherever they want to. Implicitly you want the ability to, whenever you want, assign a buyer and make sure that you know and get a piece of the action? Or in the (your) model does the seller of the ‘rental’ make the money? + +David Tam + +When I buy 3P audiences, they will be based on cookie IDs or email addresses that audiences are built around. The beauty of PA is that it doesn’t require an ID. It happens in the browser, I don’t need to know any identity, I just need to know that this person visits a pub and the pub thinks that person should belong to a certain audience. These audiences do not have IDs built into it. I could still buy 3P audiences that use cookies or email to bid on this. + +David Dabbs + +Do you want the purchasing of your audience to only happen on your property? Would it be limited to impressions on your property? + +David Tam +I think pubs would welcome monetization; given that the utility of 3P cookies is diminishing, how else to drive revenue? A lot of pubs don’t have that many 1P identifiers. + +Michael + +Often we get into trouble when one party speaks towards others’ interests. We don’t have Patrick McCann's pub POV right now, so right now we might be deficient to speak to his audience creation use cases. + +Brian + +I spent lots of time with pubs, I think the ideal model is where a pub doesn’t have to try to sell IG outright. Pubs will always over-value their IG. Might mean that buyers are not interested in IGs. If someone means that the IG owner could provide IG to the buyer, buyer gains whatever benefit and decides whether to keep using it, it would benefit the entire PA ecosystem. + +David Tam + +I concur + +Brain + +The IG creator has identified until of value, which will go to another context and be purchased, the value of that purchase will be given the provider of context. There needs to be a way of taking value of interest and context and combining them so both parties gain value at point of activation, not just creation. + +Michael + +It seems you are arguing for IG creators running an auction to find out what the value of IG bidding opp is going to be to bidders. That would be fine with me, auctions are great. Imagine pubs or other audience creators announcing to bidders their opinion of some kind of performance characteristics, and then ask for bids on how much the bidder would pay to have the opportunity to bid on that audience. Potential bidders needing to decide how much to pay for IG at time of creation, that requires some kind of prediction. But in-auction bidding ALSO requires prediction; advertisers are paying money now but are still hoping for value later on. I don’t think these are hugely different; showing an ad to a user in the moment often isn’t what gets value, it’s the conversion event. Prediction conversions based on audience attributes, it’s the uncertainty when you only know 1 domain worth of behavior. + +Brian + +There should be some kind of competition to gain access to IG. How much ownership / value does IG creator have to give up and is it a terminal event? + +Michael + +This is getting back to the July discussion. We discussed 2 things that IG creator might want to retain after the fact so as to not give up everything during IG creation: (1) money: a piece of the action that is spent later on that IG, which can be done through after-the-fact reporting. (2) control: is any control retained on how the IG is used? For the first one, the money part: If the audience creator runs an auction for who gets to have an IG created for them, then the bids in the auction could include whatever kind of payment you want. Audience creators could accept bids of the form "$X right now plus a Y% cut of the action when this IG wins the auction" for example. This still requires us, the browser, to build some new reporting mechanism so that the audience creation party can get reports when the IG layer wins. But that's OK, an entirely reasonable additional feature we could build. + +Brian + +You mentioned that the risk taken in IG creation auction is equivalent to risk in OpenRTB auction. An interest group once won has much higher potential value over 1 bid. + +David Dabbs + +In today’s world the creation of IG on pub’s site is all or nothing prop? They can do it through policy header, they would put in origins of delegates they would be permitting to create IG on their property + +David Tam + +Issue is tons of tags on the publishers’ website and this has been one of the biggest challenges of programmatic today. diff --git a/meetings/2024-08-28-FLEDGE-call-minutes.md b/meetings/2024-08-28-FLEDGE-call-minutes.md new file mode 100644 index 000000000..4489255aa --- /dev/null +++ b/meetings/2024-08-28-FLEDGE-call-minutes.md @@ -0,0 +1,226 @@ +# 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 Sept 4, 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. Paul Jensen (Google Privacy Sandbox) +1. Luckey Harpley (Remerge.io) +1. Ricardo Bentin (Media.net) +1. Harshad Mane (PubMatic) +1. Matt Kendall (Index Exchange) +1. Arthur Coleman (IDPrivacy) +1. Pooja Muhuri (Google Privacy Sandbox) +1. Matt Davies (Bidswitch | Criteo) +1. Ivan Staritskii (Bidswitch | Criteo) +1. Laurentiu Badea (OpenX) +1. Kevin Lee (Google Privacy Sandbox) +1. Laura Morinigo (Samsung) +1. Alexandre Nderagakura (Not affiliated) +1. Yanay Zimran (Start.io) +1. Matt Menke (Google Chrome) +1. Brian Schmidt (OpenX) +1. Garrett McGrath (Magnite) +1. Chris Nachmias (Flashtalking) +1. Shivani Sharma (Google Privacy Sandbox) +1. Alonso Velasquez (Google Privacy Sandbox) +1. Lydon, Jason (Ft) +1. Josh Singh (MSFT Ads) +1. Alex Peckham (Flashtalking) +1. David Tam (paapi.ai) +1. Paul Spadaccini (Flashtalking) +1. Joshua Prismon (Index Exchange) +1. Shafir Uddin (Raptive) +1. Yanush Piskevich(MSFT Ads) +1. Jeremy Bao (Google Privacy Sandbox) +1. Abishai (Google Privacy Sandbox) +1. Jeroune (Google Privacy Sandbox) +1. Kenneth Kharma (OpenX) +1. Andrew Pascoe (NextRoll) + +## Note taker: Orr Bernstein / David Tam + + +# 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 (meetings with no items may be canceled) + +* [David Tam] Add seller information to the KV server request - https://github.com/WICG/turtledove/issues/1249 +* [Matt Davies] Follow up to https://github.com/WICG/turtledove/issues/1220 to understand the next steps in getting reporting for third parties outside of the component auction but within the billing chain +* [David Dabbs] Chrome Status indicates that Chrome M130 will ship ~mid-October with Protected Audience Bidding & Auction Services enabled by default. The active (extended) Origin Trial runs through M129. Can you speak to this ‘release,’ its parity with on-device, &c, or is the PAS call series every other week after this call the place to inquire? + +# 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 + +Announcements +* TPAC in about a month’s time + * We’ve requested a meeting to talk about Protected Audience API and Microsoft’s Ad Selection API + +## Add seller information to the KV server request - [Issue 1249](https://github.com/WICG/turtledove/issues/1249) + +David Tam: One observation - what we want to be able to do is to bid from certain sellers from the buy side. While the seller can determine who the buyer is, vice versa is also useful. Buyer may want to bid for certain sellers. Request is “accepted” - only question is, when is it likely to be supported? + +Paul Jensen: You mentioned that buyers may need to bid based on hostname of the publisher? The request is about the hostname of the seller. + +David Tam: Yes, for the moment, it has the hostname along with some of the keys for the macros. What we’d really like is to see who the component seller is in that case along with that request. The way it’s proposed is as an attribute on the InterestGroup. We avoid unnecessary bits. + +Paul: Not in the IG. + +David Tam: That’s correct. In the IG, we’d add to the IG a list of sellers, and prioritize those you want to buy from. + +Paul: The priority vector - what you want to bid and the priority of that bid. + +David Tam: Yes, and sometimes not to bid. + +Paul: Two different sections per buyers. The key section - the DB lookup of a key. And an IG section. The IG section is the one with the priority vector in it. The priority vector allows you to specify different values in a vector, which gets dot-producted, and if the priority is negative, then the IG doesn’t get to bid on a particular request. One of the changes is to allow the buyer to specify which component seller each of the elements in the priority vector applies to. + +That seems fine. We don’t want to duplicate the request for different sellers. Two parts to this. The response part sounds fine. + +David Tam: Don’t really have a preference, just want this use case. + +Paul: I think we could add that support to allow the priority vector response to indicate which seller it applies to. + +David Tam: Yes, that also works. + +Paul: That would be fine then. Michael is aligned with that, and Matt also said it sounds reasonable. Do you need the request to include the list of sellers to not? + +David Tam: The reason for having it in the IG is to minimize the number of bid requests that we’ll receive, or the number that hits the K/V server. Has a number of advantages. But it could be part of the K/V response instead of the request. We could make different logical decisions based on how we intend to bid. Also has certain advantages. Having in the IG minimizes the number of K/V requests. + +Paul: What do you mean in the IG? + +David Tam: This is the list of sellers for this IG that you want to buy from. If we happen to see a request from a seller that’s not on that list, we don’t bid for it, or we don’t see that request at all. + +Paul: That’s fine. So the only change needed from the browser is to be able to break up the priority vector by component seller? + +David Tam: Yes. + +Paul: I’d be interested to hear if others are excited about this as well, to help us prioritize this. + +David Dabbs: Will folks like David who want to use this use the trusted signals slot size mode to do this? I don’t see us changing the way we bid, and wouldn’t want the seller to mess with caching. You’d want a way to opt into it and not have it happen uncontrolled. + +Paul: Last part of the conversation is that we don’t have to modify our request. David had the idea that you could include in the IG the sellers that you’re bidding against. + +David Dabbs: So, you’re taking up space in the IGs. Why wouldn’t it be in your bidding logic? + +David Tam: More unnecessary bidding, invocations of the generateBid() function. If you don’t intend to bid, why are you invoking it. + +David Dabbs: Bigger cost to you is the trusted signals call. + +David Tam: Yes, if I don’t intend to bid, I don’t want the request. + +David Dabbs: But you’ll still get the request. + +David Tam: Not if it’s in the InterestGroup. Then we don’t get the request at all. + +Paul: Specific priority vector would be specific to a given component seller. + +## Follow up to [Issue 1220](https://github.com/WICG/turtledove/issues/1220) to understand the next steps in getting reporting for third parties outside of the component auction but within the billing chain + +Matt Davies: 5 or 6 meetings back, chatting to Alonso, the addition of adding 3rd party reporting. Some one in the middle is part of the bidding chain, but not necessarily the only route to traffic. Some way to go through bidswitch. Can SSP and DSP on behalf of bidswitch, require on 3rd party for accurate reporting. Feedback on simplified option. + +Paul: Bidswitch as component Seller. Situation when bids pass through bidswitch. Beacon that allows one URL per bid. + +Matt Davies: Potentially get a worklet to run. Basically impression and click cost data. Verified by the SSP/DSP so we can bill on that transaction. It does not make sense for us to be a component Seller. Then SSP cannot be a component Seller. Just want to receive accurate information going through Bidswitch. When going through Bidswitch we can simply add our reporting URL. This is the Bidswitch reporting URL then we would have access to the data. + +Shivani:RegisterAdMacro is a functionality that is available. Key value pairs can be registered by the DSP and reported in an ad frame to a URL which could be the 3P URL. That request can go to Bidswitch. It does require coordinate between Bidswitch and buyer. + +Ivan: Requires some initiation with the buyer. Seller and Buyer agrees on using event name impression. Can trigger an impression event. If go via Bidswitch we can trigger the impression event. To keep the logic of impression inside the worklet. + +Alonso: Summarise the gaps. Mostly a problem on coordination between Bidswitch and the buy side. + +Matt Davies: In order to have accurate tracking. Be independently track impression from a DSP. There is no way to coordinate with the DSP. If something went wrong then we Bidswitch need to be able identify what went wrong. When it goes through Bidswitch then it needs to be done by us. It could go through a SSP, but it ought to go through our logic code. + +Alonso:Makes sense. Infrastructure we have today for 3rd parties. Rely report for the larger buy side. When there are massive campaigns are run. They have 3rd party reporting needs. DSPs fulfill the campaign etc. In fenced frame reporting, quite useful in supporting 3rd parties. Somewhat similar capabilities to sell side reporting. Gap maybe, from a workflow, for you Bidswitch as a reporting vendor. + +Matt Davies: We need to bill and invoice. From a purely financial trade this breaks. This causes problems. Either we disappear as we become irrelevant. + +Alonso: Here we have a reporting Use Case that is not clearly for Buy or Sell side. This particular Use Case is a hybrid situation. Similar of adjacent realm needs to be encourages. Different companies have a different role. We can internally understand demand and priorities. Let’s see what are the demand. + +Matt Davies: We are somewhat unique. + +Paul: Which buyers signals would that be. + +Matt Davies: What happens, DSP is connected to SSP via Bidswitch. DSP would respond with and we would submit this to the SSP. We have the option from a logic point of view, this went via Bidswitch. The details is that we could place a reporting URL. Through conversations in the past the buyer can insert multiple reporting URL. It would be simple if that bid went via Bidswitch. This went via Bidswitch. When we receive the IG bid we have theoption we can add x t this. If this goes down the chain then we know that this took place. Then we get the reporting on the impression. + +Paul: Which auction, PA or contextual. Which buyer is adding this. + +Matt Davies: Open RTB we get contextual .. The IG for the PA auction, what ever response we get from DSP we pass to the Seller. It is part of PA auction. ORTB we are fine that flow is sorted. We would know automatically. When it comes to PA version the reporting is only sent to the reporting API. + +Ivan: FContextual bid, in the seller extension provides the reporting URL. The worklet of seller side send report to Bidswitch. It is only possible to call only once. We cannot use this functionality. Discuss with SSP to send report on our impression URL. + +Paul: Last call in July. Bidswitch works with a whole bunch of DSPs. Some component seller would need to on the buyer list. Bidswitch conveying this list of buyers to seller. + +Ivan: Buyers that are willing to participate, SSP create the auctions. + +Paul: You give the list to Sellers. + +Ivan: Not contextual bids. perBuyerSignals to each DSP. + +Paul:Assuming integration with SSP, providing a list of DSPs as potential buyers. Bids coming from these buyers would go to Bidswitch. + +Ivan: Allow to call sendReportTo function multiple times. Buyer to add another event if possible. Another way to sendReportTo to call the event name only once but multiple times. One for th SSP another for Bidswitch. Also we can solve reporting without complicated mechanics. + +Paul: Sounds possible. Do foresee that may not work great to parallise with PA auction. + +Ivan: We can provide a list of buyers. Run auction with a list of our buyers. + +Paul: I think it could work. If we avoid some latency. + +Shivani: SQuestion, if there was support for 3rd party reporting then what about ad rendering. + +Ivan: Impression that happen inside of the creative is important that happens inside the worklet. Central reporting tool is optional. + +Shivani: Without any changes …. If this is registerAdBeacon with a unique event name e.g. “impression_3p”, called inside the ad frame then there are no changes. + +Ivan: It is impossible to agree something with the DSP but hard (possible) to coordinate with SSP. + +Shivani: DSP calls the event, then it is hard to coordinate. We also have automatic event. If it is part of the ad rendering request, then entity that this registered for automatic beacon thn they will get the event. + +Ivan: We are only interested in the impression, not click. + +Paul: Ponder about the privacy properties to address this. Not sure about the implications. + +## Chrome Status indicates that Chrome M130 will ship ~mid-October with Protected Audience Bidding & Auction Services enabled by default. + +Paul: When is Chrome supporting BA services. + +David Dabbs: It is on default version 130. Is this real? + +Paul: Chrome dev stuff is forward looking. I did not put the 130 in there. Do not want to go past 12 months on the testing. Discussing this soon. We will have to make some announcement soon. + +David Dabbs: Process question. There have been a lot of changes to the web API. + +Paul: Happy to talk about the web API. in terms of published timeline. + +David Dabbs: Sometime in Q3 of this year. + +Paul: Timeline for server side, will place a link. diff --git a/spec.bs b/spec.bs index 38824fe1e..926ac193e 100644 --- a/spec.bs +++ b/spec.bs @@ -178,6 +178,12 @@ advertisement relevant to this interest to this user in the future. The [=user a

joinAdInterestGroup()

+Issue: TODO: Currently, several of the IDL fields in {{AuctionAdInterestGroup}} are specified to use +USVString rather than DOMString, only because the initial implementation currently does. This may +contradict the recommended use of +DOMString. +(WICG/turtledove#1250) + [SecureContext] partial interface Navigator { @@ -186,17 +192,23 @@ partial interface Navigator { dictionary AuctionAd { required USVString renderURL; + USVString sizeGroup; any metadata; USVString buyerReportingId; USVString buyerAndSellerReportingId; sequence<USVString> allowedReportingOrigins; + DOMString adRenderId; +}; + +dictionary AuctionAdInterestGroupSize { + required USVString width; + required USVString height; }; dictionary GenerateBidInterestGroup { required USVString owner; required USVString name; - required double lifetimeMs; boolean enableBiddingSignalsPrioritization = false; record<DOMString, double> priorityVector; @@ -213,11 +225,14 @@ dictionary GenerateBidInterestGroup { any userBiddingSignals; sequence<AuctionAd> ads; sequence<AuctionAd> adComponents; + record<DOMString, AuctionAdInterestGroupSize> adSizes; + record<DOMString, sequence<DOMString>> sizeGroups; }; dictionary AuctionAdInterestGroup : GenerateBidInterestGroup { double priority = 0.0; record<DOMString, double> prioritySignalsOverrides; + required double lifetimeMs; DOMString additionalBidKey; }; @@ -226,7 +241,11 @@ dictionary AuctionAdInterestGroup : GenerateBidInterestGroup { when an interest group is stored to [=interest group set=]. `priority` and `prioritySignalsOverrides` are not passed to `generateBid()` because 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. +a user accessible to `generateBid()` methods, otherwise. `lifetimeMs` is not passed to `generateBid()` +because 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.
@@ -234,9 +253,9 @@ The joinAdInterestGroup(|group|) method steps ar
Temporarily, Chromium does not include the required keyword -for {{GenerateBidInterestGroup/lifetimeMs}}, and instead starts this algorithm with the step +for {{AuctionAdInterestGroup/lifetimeMs}}, and instead starts this algorithm with the step -1. If |group|["{{GenerateBidInterestGroup/lifetimeMs}}"] does not [=map/exist=], throw a {{TypeError}}. +1. If |group|["{{AuctionAdInterestGroup/lifetimeMs}}"] does not [=map/exist=], throw a {{TypeError}}. This is detectable because it can change the set of fields that are read from the argument when a {{TypeError}} is eventually thrown, but it will never change whether the call succeeds or fails. @@ -245,12 +264,13 @@ This is detectable because it can change the set of fields that are read from th 1. Let |global| be [=this=]'s [=relevant global object=]. 1. If |global|'s [=associated Document=] is not [=allowed to use=] the "[=join-ad-interest-group=]" [=policy-controlled feature=], then [=exception/throw=] a "{{NotAllowedError}}" {{DOMException}}. -1. Let |frameOrigin| be [=this=]'s [=relevant settings object=]'s [=environment settings object/origin=]. -1. [=Assert=] that |frameOrigin| is not an [=opaque origin=] and its [=origin/scheme=] is "`https`". +1. Let |settings| be [=this=]'s [=relevant settings object=]. +1. [=Assert=] that |settings|'s [=environment settings object/origin=] is not an [=opaque origin=] + and its [=origin/scheme=] is "`https`". 1. Let |interestGroup| be a new [=interest group=]. 1. Validate the given |group| and set |interestGroup|'s fields accordingly. 1. Set |interestGroup|'s [=interest group/expiry=] to the [=current wall time=] plus - |group|["{{GenerateBidInterestGroup/lifetimeMs}}"] milliseconds. + |group|["{{AuctionAdInterestGroup/lifetimeMs}}"] milliseconds. 1. Set |interestGroup|'s [=interest group/next update after=] to the [=current wall time=] plus 24 hours. 1. Set |interestGroup|'s [=interest group/last updated=] to the [=current wall time=]. @@ -339,6 +359,26 @@ This is detectable because it can change the set of fields that are read from th "slot-size", or "all-slots-requested-sizes", set |interestGroup|'s [=interest group/trusted bidding signals slot size mode=] to |group|["{{GenerateBidInterestGroup/trustedBiddingSignalsSlotSizeMode}}"]. + 1. Let |adSizes| be a new [=map=] whose [=map/keys=] are [=strings=] and [=map/values=] are + [=ad sizes=]. + 1. If |group|["{{GenerateBidInterestGroup/adSizes}}"] [=map/exists=]: + 1. [=map/For each=] |sizeName| → |size| of |group|["{{GenerateBidInterestGroup/adSizes}}"]: + 1. If |sizeName| is "", [=exception/throw=] a {{TypeError}}. + 1. Let |parsedSize| be the result from running [=parse an AdRender ad size=] with |size|. + 1. If |parsedSize| is null, [=exception/throw=] a {{TypeError}}. + 1. [=map/Set=] |adSizes|[|sizeName|] to |parsedSize|. + 1. Set |interestGroup|'s [=interest group/ad sizes=] to |adSizes|. + 1. Let |sizeGroups| be a new [=map=] whose [=map/keys=] are [=strings=] and [=map/values=] + are [=lists=] of [=strings=] + 1. If |group|["{{GenerateBidInterestGroup/sizeGroups}}"] [=map/exists=]: + 1. [=map/For each=] |sizeGroupName| → |sizeList| of + |group|["{{GenerateBidInterestGroup/sizeGroups}}"]: + 1. If |sizeGroupName| is "", [=exception/throw=] a {{TypeError}}. + 1. [=list/For each=] |sizeName| of |sizeList|: + 1. If |sizeName| is "" or |adSizes|[|sizeName|] does not [=map/exist=], + [=exception/throw=] a {{TypeError}}. + 1. [=map/Set=] |sizeGroups|[|sizeGroupName|] to |sizeList|. + 1. Set |interestGroup|'s [=interest group/size groups=] to |sizeGroups|. 1. For each |groupMember| and |interestGroupField| in the following table @@ -360,10 +400,20 @@ This is detectable because it can change the set of fields that are read from th * |renderURL| [=url/scheme=] is not "`https`"; * |renderURL| [=includes credentials=]. 1. Set |igAd|'s [=interest group ad/render url=] to |renderURL|. + 1. If |ad|["{{AuctionAd/sizeGroup}}"] [=map/exists=]: + 1. Let |sizeGroup| be |ad|["{{AuctionAd/sizeGroup}}}]. + 1. [=exception/Throw=] a {{TypeError}} if none of the following conditions hold: + * |adSizes|[|sizeGroup|] [=map/exists=]. + * |sizeGroups|[|sizeGroup|] [=map/exists=]. + 1. Set |igAd|'s [=interest group ad/size group=] to |sizeGroup|. 1. If |ad|["{{AuctionAd/metadata}}"] [=map/exists=], then let |igAd|'s [=interest group ad/metadata=] be the result of [=serializing a JavaScript value to a JSON string=], given |ad|["{{AuctionAd/metadata}}"]. This can [=exception/throw=] a {{TypeError}}. + 1. If |ad|["{{AuctionAd/adRenderId}}"] [=map/exists=]: + 1. If |ad|["{{AuctionAd/adRenderId}}"]'s [=string/length=] > 12, [=exception/throw=] a {{TypeError}}. + 1. If any [=code point=] in |ad|["{{AuctionAd/adRenderId}}"] is not an [=ASCII code point=], [=exception/throw=] a {{TypeError}}. + 1. Set |igAd|'s [=interest group ad/ad render id=] to |ad|["{{AuctionAd/adRenderId}}"]. 1. If |groupMember| is "{{GenerateBidInterestGroup/ads}}": 1. If |ad|["{{AuctionAd/buyerReportingId}}"] [=map/exists=], then set |igAd|'s [=interest group ad/buyer reporting ID=] to it. @@ -389,13 +439,13 @@ 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=]. 1. [=parallel queue/enqueue steps|Enqueue the following steps=] to |queue|: 1. Let |permission| be the result of [=checking interest group permissions=] with - |interestGroup|'s [=interest group/owner=], |frameOrigin|, and "`join`". + |interestGroup|'s [=interest group/owner=], |settings|, and "`join`". 1. If |permission| is false, then [=queue a global task=] on [=DOM manipulation task source=], given |global|, to [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and abort these steps. @@ -413,6 +463,7 @@ This is detectable because it can change the set of fields that are read from th the current day in UTC, increment its count. If not, [=list/insert=] a new [=tuple=] the time set to the current UTC day and a count of 1. 1. Store |interestGroup| in the [=user agent=]'s [=interest group set=]. + 1. Run [=update k-anonymity cache for interest group=] for |interestGroup|. 1. Return |p|. @@ -434,7 +485,7 @@ starting point, inspired by what the initial implementation of this specificatio [=interest group/owner=]. * Max interest groups total size per owner is 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=].
@@ -478,7 +529,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=] @@ -530,7 +581,8 @@ 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 |settings| be [=this=]'s [=relevant settings object=]. +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 |p| be [=a new promise=]. 1. If |group| [=map/is empty=]: @@ -556,8 +608,8 @@ The leaveAdInterestGroup(group) method steps are |group|["{{AuctionAdInterestGroupKey/owner}}"]. 1. If |owner| is failure, [=exception/throw=] a {{TypeError}}. 1. Run these steps [=in parallel=]: - 1. Let |permission| be the result of [=checking interest group permissions=] with - |owner|, |frameOrigin|, and "`leave`". + 1. Let |permission| be the result of [=checking interest group permissions=] with |owner|, + |settings|, and "`leave`". 1. If |permission| is false, then [=queue a global task=] on [=DOM manipulation task source=], given |global|, to [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and abort these steps. @@ -592,8 +644,8 @@ partial interface Navigator { The clearOriginJoinedAdInterestGroups(|owner|, |interestGroupsToKeep|) method steps are: -1. Let |frameOrigin| be [=this=]'s [=relevant settings object=]'s - [=environment settings object/origin=]. +1. Let |settings| be [=this=]'s [=relevant settings object=]. +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 |p| be [=a new promise=]. 1. Let |global| be [=this=]'s [=relevant global object=]. @@ -606,7 +658,7 @@ method steps are: 1. If |ownerOrigin| is failure, [=exception/throw=] a {{TypeError}}. 1. Run these steps [=in parallel=]: 1. Let |permission| be the result of [=checking interest group permissions=] with - |ownerOrigin|, |frameOrigin|, and "`leave`". + |ownerOrigin|, |settings|, and "`leave`". 1. If |permission| is false, then [=queue a global task=] on the [=DOM manipulation task source=] given |global|, [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and abort these steps. 1. [=Queue a global task=] on the [=DOM manipulation task source=] given |global|, to [=resolve=] |p| @@ -637,6 +689,10 @@ partial interface Navigator { readonly attribute boolean deprecatedRunAdAuctionEnforcesKAnonymity; }; +dictionary AuctionRealTimeReportingConfig { + required DOMString type; +}; + dictionary AuctionAdConfig { required USVString seller; required USVString decisionLogicURL; @@ -665,9 +721,14 @@ dictionary AuctionAdConfig { sequence> allSlotsRequestedSizes; Promise additionalBids; DOMString auctionNonce; + AuctionRealTimeReportingConfig sellerRealTimeReportingConfig; + record perBuyerRealTimeReportingConfig; sequence componentAuctions = []; AbortSignal? signal; Promise resolveToConfig; + + Promise serverResponse; + USVString requestId; }; @@ -678,7 +739,9 @@ 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. +See +https://developers.google.com/privacy-sandbox/relevance/protected-audience-api/k-anonymity for +more up to date information.
@@ -712,16 +775,26 @@ The runAdAuction(|config|) method steps are: 1. [=AbortSignal/Add|Add the following abort steps=] to |signal|: 1. [=Reject=] |p| with |signal|’s [=AbortSignal/abort reason=]. 1. Run [=update bid counts=] with |bidIgs|. - 1. Run [=interest group update=] with |auctionConfig|'s - [=auction config/interest group buyers=]. + 1. Run [=interest group update=] with |auctionConfig|'s [=auction config/interest group buyers=] + and |settings|'s [=environment settings object/policy container=]. +1. [=Assert=] that |settings|'s [=environment settings object/origin=] 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 |winnerInfo| be the result of running [=generate and score bids=] with |auctionConfig|, - null, |global|, |settings|'s [=environment/top-level origin=], |bidIgs|, and |bidDebugReportInfoList|. + 1. If |auctionConfig|'s [=auction config/server response=] is not null: + 1. Let |winnerInfo| be the result of running [=parse and validate server response=] with |auctionConfig|, + null, |global|, |bidIgs|, and |bidDebugReportInfoList|. + 1. Otherwise: + 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|, |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: @@ -729,11 +802,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 |settings|. + 1. [=Send real time reports=] with |auctionReportInfo|'s + [=auction report info/real time reporting contributions map=] and |settings|. 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 |settings|. 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. @@ -741,7 +816,8 @@ The runAdAuction(|config|) method steps are: 1. If |auctionConfig|'s [=auction config/resolve to config=] is false, then set |result| to |urn|. 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |global|, to resolve |p| with |result|. - 1. Run [=interest group update=] with |auctionConfig|'s [=auction config/interest group buyers=]. + 1. Run [=interest group update=] with |auctionConfig|'s [=auction config/interest group buyers=] + and |settings|'s [=environment settings object/policy container=]. 1. Run [=update bid counts=] with |bidIgs|. 1. Run [=update previous wins=] with |winner|. 1. Return |p|. @@ -762,7 +838,10 @@ To construct a pending fenced frame config given an [=auction config= :: "`opaque`" : [=fenced frame config/container size=] - :: TODO: fill this in once container size is spec'd to be in |config| + :: the result of + + Issue: converting |config|'s [=auction config/requested size=] to pixel values + (WICG/turtledove#986) : [=fenced frame config/content size=] :: null @@ -783,12 +862,12 @@ To construct a pending fenced frame config given an [=auction config= : [=fenced frame config/on navigate callback=] :: null - : [=fenced frame config/effective sandbox flags=] + : [=fenced frame config/effective sandboxing flags=] :: a [=struct=] with the following [=struct/items=]: - : [=effective sandbox flags/value=] + : [=effective sandboxing flags/value=] :: TODO: fill this in once fenced frame sandbox flags are more fully specified - : [=effective sandbox flags/visibility=] + : [=effective sandboxing flags/visibility=] :: "`opaque`" : [=fenced frame config/effective enabled permissions=] @@ -823,8 +902,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 [=environment settings object=] |settings|: 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=]. @@ -895,8 +974,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 |settings|. 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 @@ -918,7 +997,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 [=environment settings object=] |settings|: 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=] @@ -944,8 +1023,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 |settings|. 1. Set |buyerDone| to true. 1. If |sellerDone| is false and |leadingBidInfo|'s [=leading bid info/seller reporting result=] is not null: @@ -954,8 +1033,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 |settings|. 1. Set |sellerDone| to true. 1. If |componentSellerDone| is false and |leadingBidInfo|'s [=leading bid info/component seller reporting result=] is not null: @@ -965,13 +1044,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 |settings|. 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 |settings|. 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 |settings|. +1. [=Send real time reports=] with |auctionReportInfo|'s + [=auction report info/real time reporting contributions map=] and |settings|.
@@ -1011,12 +1092,12 @@ To create nested configs given [=generated bid/ad component descripto : [=fenced frame config/on navigate callback=] :: null - : [=fenced frame config/effective sandbox flags=] + : [=fenced frame config/effective sandboxing flags=] :: a [=struct=] with the following [=struct/items=]: - : [=effective sandbox flags/value=] + : [=effective sandboxing flags/value=] :: TODO: fill this in once fenced frame sandbox flags are more fully specified - : [=effective sandbox flags/visibility=] + : [=effective sandboxing flags/visibility=] :: "`opaque`" : [=fenced frame config/effective enabled permissions=] @@ -1059,11 +1140,22 @@ To validate and convert auction ad config given an {{AuctionAdConfig} 1. Let |seller| be the result of [=parsing an https origin=] with |config|["{{AuctionAdConfig/seller}}"]. 1. If |seller| is failure, then return failure. 1. Set |auctionConfig|'s [=auction config/seller=] to |seller|. -1. Let |decisionLogicURL| be the result of running the [=URL parser=] on - |config|["{{AuctionAdConfig/decisionLogicURL}}"]. -1. If |decisionLogicURL| is failure, or it is not [=same origin=] with |auctionConfig|'s - [=auction config/seller=], then return failure. -1. Set |auctionConfig|'s [=auction config/decision logic url=] to |decisionLogicURL|. +1. If |config|["{{AuctionAdConfig/serverResponse}}"] [=map/exists=]: + 1. If |config|["{{AuctionAdConfig/requestId}}"] does not [=map/exist=], then [=exception/throw=] + a {{TypeError}}. + 1. Set |auctionConfig|'s [=auction config/server response=] to |config|["{{AuctionAdConfig/serverResponse}}"]. + 1. Set |auctionConfig|'s [=auction config/server response id=] to |config|["{{AuctionAdConfig/requestId}}"]. +1. Otherwise: + 1. If |config|["{{AuctionAdConfig/requestId}}"] [=map/exists=], then [=exception/throw=] + a {{TypeError}}. + 1. If |config|["{{AuctionAdConfig/decisionLogicURL}}"] does not [=map/exist=], + then [=exception/throw=] a {{TypeError}}. +1. If |config|["{{AuctionAdConfig/decisionLogicURL}}"] [=map/exists=]: + 1. Let |decisionLogicURL| be the result of running the [=URL parser=] on + |config|["{{AuctionAdConfig/decisionLogicURL}}"]. + 1. If |decisionLogicURL| is failure, or it is not [=same origin=] with |auctionConfig|'s + [=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 [=parse and verify a trusted signals URL=] on |config|["{{AuctionAdConfig/trustedScoringSignalsURL}}"]. @@ -1326,6 +1418,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. @@ -1383,12 +1485,14 @@ To validate and convert auction ad config given an {{AuctionAdConfig} [=interest group/name=] is |ig|'s [=interest group/name=], return if none found. 1. Let |win| be a new [=previous win=]. 1. Set |win|'s [=previous win/time=] to the [=current wall time=]. - 1. Let |ad| be an [=interest group ad=] whose [=interest group ad/render url=] is |bid|'s - [=generated bid/bid ad=]'s [=interest group ad/render url=], and whose - [=interest group ad/metadata=] is |bid|'s [=generated bid/bid ad=]'s - [=interest group ad/metadata=]. - 1. Set |win|'s [=previous win/ad json=] to the result of - [=serializing an Infra value to a JSON string=] given |ad|. + 1. Let |ad| be a new [=interest group ad=] with the following [=struct/items=]: + : [=interest group ad/render url=] + :: |bid|'s [=generated bid/bid ad=]'s [=interest group ad/render url=] + : [=interest group ad/metadata=] + :: |bid|'s [=generated bid/bid ad=]'s [=interest group ad/metadata=] + : [=interest group ad/ad render ID=] + :: |bid|'s [=generated bid/bid ad=]'s [=interest group ad/ad render ID=] + 1. Set |win|'s [=previous win/ad=] to |ad|. 1. [=list/Append=] |win| to |loadedIg|'s [=interest group/previous wins=]. 1. [=list/Replace=] the [=interest group=] that has |loadedIg|'s [=interest group/owner=] and [=interest group/name=] in the [=user agent=]'s [=interest group set=] with |loadedIg|. @@ -1470,10 +1574,12 @@ To generate potentially multiple bids given an [=ordered map=]-or-nul 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 +[=environment settings object=] |settings|, 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=] @@ -1488,21 +1594,27 @@ a {{DirectFromSellerSignalsForBuyer}} |directFromSellerSignalsForBuyer|, a [=dur 1. Let |timeDelta| be |auctionStartTime| minus |prevWin|'s [=previous win/time=]. 1. Set |timeDelta| to 0 if |timeDelta| is negative, |timeDelta|'s nearest second (rounding down) otherwise. - 1. Let |prevWinIDL| be a new {{PreviousWin}}. - 1. [=map/Set=] |prevWinIDL|["{{PreviousWin/timeDelta}}"] to |timeDelta|. - 1. [=map/Set=] |prevWinIDL|["{{PreviousWin/adJSON}}"] to |prevWin|'s [=previous win/ad json=]. - 1. [=list/Append=] |prevWinIDL| to |prevWins|. + 1. Let |prevWinAdIDL| be a new {{AuctionAd}} with the following [=struct/items=]: + : {{AuctionAd/renderURL}} + :: the [=URL serializer|serialization=] of |prevWin|'s [=interest group ad/render url=] + : {{AuctionAd/metadata}} + :: |prevWin|'s [=interest group ad/metadata=] + : {{AuctionAd/adRenderId}} + :: |prevWin|'s [=interest group ad/ad render ID=] + 1. Let |prevWinElement| be the [=sequence=]<{{PreviousWinElement}}> «|timeDelta|, |prevWinAdIDL|». + 1. [=list/Append=] |prevWinElement| to |prevWins|. 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/prevWinsMs}}"] to |prevWins|. 1. Let |biddingScriptFetcher| be the result of [=creating a new script fetcher=] with - |ig|'s [=interest group/bidding url=]. + |ig|'s [=interest group/bidding url=], and |settings|. 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 |settings|. 1. If |wasmModuleObject| is not failure, then [=map/set=] |browserSignals|["{{BiddingBrowserSignals/wasmHelper}}"] to |wasmModuleObject|. + 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. @@ -1531,14 +1643,18 @@ a {{DirectFromSellerSignalsForBuyer}} |directFromSellerSignalsForBuyer|, a [=dur
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|: +[=auction config=]-or-null |topLevelAuctionConfig|, a [=global object=] |global|, 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 |topLevelOrigin| be |settings|'s [=environment/top-level origin=]. +1. Let |seller| be |auctionConfig|'s [=auction config/seller=]. 1. Let |auctionStartTime| be the [=current wall time=]. 1. Let |decisionLogicFetcher| be the result of [=creating a new script fetcher=] with - |auctionConfig|'s [=auction config/decision logic url=]. -1. Let |seller| be |auctionConfig|'s [=auction config/seller=]. + |auctionConfig|'s [=auction config/decision logic url=] and |settings|. +1. Let |trustedScoringSignalsBatcher| be the result of [=creating a trusted scoring signals + batcher=] with |auctionConfig|'s [=auction config/max trusted scoring signals url length=]. 1. Let « |bidGenerators|, |negativeTargetInfo| » be the result of running [=build bid generators map=] with |auctionConfig|. 1. Let |leadingBidInfo| be a new [=leading bid info=]. @@ -1554,7 +1670,7 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. [=list/For each=] |component| in |auctionConfig|'s [=auction config/component auctions=], [=parallel queue/enqueue steps|enqueue the following steps=] to |queue|: 1. Let |compWinnerInfo| be the result of running [=generate and score bids=] with |component|, - |auctionConfig|, |global|, |topLevelOrigin|, |bidIgs|, and |bidDebugReportInfoList|. + |auctionConfig|, |global|, |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. @@ -1562,18 +1678,19 @@ 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|, |decisionLogicFetcher|, - null, "top-level-auction", null, and |topLevelOrigin|. + |trustedScoringSignalsBatcher|, 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|, |decisionLogicFetcher|, null, - "top-level-auction", null, and |topLevelOrigin|. + |leadingBidInfo|, |decisionLogicFetcher|, |trustedScoringSignalsBatcher|, + |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. @@ -1600,8 +1717,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 |settings|. 1. Return |leadingBidInfo|. 1. If [=waiting until configuration input promises resolve=] given |auctionConfig| returns failure, @@ -1621,7 +1738,7 @@ 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=] +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". @@ -1639,10 +1756,10 @@ 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|, - |decisionLogicFetcher|, null, |auctionLevel|, - |componentAuctionExpectedCurrency|, and |topLevelOrigin|. + |decisionLogicFetcher|, |trustedScoringSignalsBatcher|, |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 @@ -1687,6 +1804,9 @@ 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 |crossOriginTrustedBiddingSignalsOrigin| be null. @@ -1697,7 +1817,8 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. [=map/For each=] joiningOrigin → |groups| of |perSignalsUrlGenerator|: 1. [=list/For each=] |ig| of |groups|: 1. [=Batch or fetch trusted bidding signals=] given |trustedBiddingSignalsBatcher|, - |ig|, |signalsUrl|, |buyerExperimentGroupId|, |topLevelOrigin|, and |slotSizeQueryParam|. + |ig|, |signalsUrl|, |buyerExperimentGroupId|, |topLevelOrigin|, |slotSizeQueryParam|, + and |settings|'s [=environment settings object/policy container=]. 1. [=Fetch the current outstanding trusted signals batch=] given |trustedBiddingSignalsBatcher|, |signalsUrl|, |buyer|, |buyerExperimentGroupId|, |topLevelOrigin|, and |slotSizeQueryParam|. 1. [=Process updateIfOlderThanMs=] with |buyer|, and |trustedBiddingSignalsBatcher|'s @@ -1719,11 +1840,17 @@ To generate and score bids given an [=auction config=] |auctionConfig [=interest group/owner=]. 1. Let |dataVersion| be null. 1. Let |allTrustedBiddingSignals| be an [=ordered map=]-or-null, initially null. - 1. If |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/no signals flags=] - [|ig|'s [=interest group/name=]] is false: - 1. Set |dataVersion| to |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/data versions=] - [|ig|'s [=interest group/name=]]. + 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: @@ -1731,11 +1858,16 @@ To generate and score bids given an [=auction config=] |auctionConfig |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|, and |auctionStartTime|. + 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 |settings|. + 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 @@ -1758,25 +1890,25 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. If |originalAds| is not null: 1. Set |ig|'s [=interest group/ads=] to a new [=list=] of [=interest group ad=]. 1. [=list/For each=] |ad| in |originalAds|: - 1. If [=query ad k-anonymity count=] given |ig| and |ad|'s - [=interest group ad/render url=] returns true, [=list/append=] |ad| to |ig|'s - [=interest group/ads=]. + 1. Compute |adHashCode| by getting the result of [=compute the key hash of ad=] given |ig| and |ad|. + 1. If [=query k-anonymity cache=] given |adHashCode| returns true, + [=list/append=] |ad| to |ig|'s [=interest group/ads=]. 1. Let |originalAdComponents| be |ig|'s [=interest group/ad components=]. 1. If |originalAdComponents| is not null: 1. Set |ig|'s [=interest group/ad components=] to a new [=list=] of [=interest group ad=]. 1. [=list/For each=] |adComponent| in |originalAdComponents|: - 1. If [=query component ad k-anonymity count=] given |adComponent|'s - [=interest group ad/render url=] returns true, [=list/append=] |adComponent| to |ig|'s - [=interest group/ad components=]. + 1. Compute |componentAdHashCode| by getting the result of [=compute the key hash of component ad=] given |adComponent|. + 1. If [=query k-anonymity cache=] given |componentAdHashCode| returns true, + [=list/append=] |adComponent| to |ig|'s [=interest group/ad components=]. 1. If |perBuyerCumulativeTimeout| is not null and is < |perBuyerTimeout|, then set |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|, |crossOriginTrustedBiddingSignalsOrigin|, |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 |settings|. Note: passing 1 for multiBidLimit limits the rerun to producing at most a single bid. @@ -1793,12 +1925,16 @@ 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|, - |decisionLogicFetcher|, |directFromSellerSignalsForSeller|, |dataVersion|, - |auctionLevel|, |componentAuctionExpectedCurrency|, and |topLevelOrigin|. + |decisionLogicFetcher|, |trustedScoringSignalsBatcher|, + |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. @@ -1809,18 +1945,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 |settings|. 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|.
@@ -1874,6 +2015,8 @@ To convert to an AuctionAd sequence given a [=list=]-or-null |ads|: 1. Let |adIDL| be a new {{AuctionAd}}. 1. [=map/Set=] |adIDL|["{{AuctionAd/renderURL}}"] to the [=URL serializer|serialization=] of |ad|'s [=interest group ad/render url=]. + 1. If |ad|'s [=interest group ad/size group=] is not null, then [=map/set=] + |adIDL|["{{AuctionAd/sizeGroup}}"] to |ad|'s [=interest group ad/size group=]. 1. If |ad|'s [=interest group ad/metadata=] is not null, then [=map/set=] |adIDL|["{{AuctionAd/metadata}}"] to the result of [=parsing a JSON string to a JavaScript value=] given |ad|'s [=interest group ad/metadata=]. @@ -1882,11 +2025,12 @@ To convert to an AuctionAd sequence given a [=list=]-or-null |ads|:
-To fetch and decode trusted scoring signals given an [=auction config=] |auctionConfig|, -a [=generated bid=] |generatedBid|, a [=script fetcher=] |decisionLogicFetcher|, and an [=origin=] -|topLevelOrigin|: +To fetch and decode trusted scoring signals given a [=trusted scoring signals batcher=] +|batcher|, an [=auction config=] |auctionConfig|, a [=generated bid=] |generatedBid|, a +[=script fetcher=] |decisionLogicFetcher|, an [=origin=] |topLevelOrigin|, a [=real time reporting +contributions map=] |realTimeContributionsMap|, and a [=policy container=] |policyContainer|: -1. Let |crossOriginTrustedScoringSignalsOrigin| be null. +1. Let |isCrossOrigin| be false. 1. Let |sameOriginTrustedScoringSignals| be null. 1. Let |crossOriginTrustedScoringSignals| be null. 1. Let |scoringDataVersion| be null. @@ -1898,43 +2042,41 @@ a [=generated bid=] |generatedBid|, a [=script fetcher=] |decisionLogicFetcher|, descriptors=]: 1. [=list/Append=] [=URL serializer|serialized=] |adComponentDescriptor|'s [=ad descriptor/url=] to |adComponentRenderURLs|. -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. 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 an [=ordered map=]: + 1. Let |request| be a new [=trusted scoring signals request=] with the following [=struct/items=]: + : [=trusted scoring signals request/seller=] + :: |auctionConfig|'s [=auction config/seller=] + + : [=trusted scoring signals request/seller script fetcher=] + :: |decisionLogicFetcher| + + : [=trusted scoring signals request/base url=] + :: |auctionConfig|'s [=auction config/trusted scoring signals url=] + + : [=trusted scoring signals request/seller experiment group id=] + :: |auctionConfig|'s [=auction config/seller experiment group id=] + + : [=trusted scoring signals request/top level origin=] + :: |topLevelOrigin| + + : [=trusted scoring signals request/render URL=] + :: |renderURL| + + : [=trusted scoring signals request/ad component URLs=] + :: |adComponentRenderURLs| + + : [=trusted scoring signals request/policy container=] + :: |policyContainer| + + 1. If |auctionConfig|'s [=auction config/trusted scoring signals url=]'s [=url/origin=] is not + [=same origin=] with |auctionConfig|'s [=auction config/seller=], then set |isCrossOrigin| to + true. + 1. Let |result| be the result of [=fetching trusted scoring signals with batching=] with + |batcher| and |request|. + 1. If |result| is not failure: + 1. Let |allTrustedScoringSignals| be |result|'s [=trusted scoring signals reply/all trusted + scoring signals=]: + 1. Set |scoringDataVersion| to |result|'s [=trusted scoring signals reply/data version=]. 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 @@ -1949,37 +2091,40 @@ a [=generated bid=] |generatedBid|, a [=script fetcher=] |decisionLogicFetcher|, [=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| + 1. If |isCrossOrigin| is false, 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|. + |auctionConfig|'s [=auction config/trusted scoring signals url=]'s [=url/origin=]. 1. [=map/Set=] |crossOriginTrustedScoringSignals|[|originKey|] to |trustedScoringSignals|. -1. Return «|crossOriginTrustedScoringSignalsOrigin|, |sameOriginTrustedScoringSignals|, - |crossOriginTrustedScoringSignals|, |scoringDataVersion|» + 1. Otherwise if |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 |auctionConfig|'s [=auction config/seller=]. +1. Return «|isCrossOrigin|, |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 [=script fetcher=] |decisionLogicFetcher|, a [=trusted scoring signals batcher=] |trustedScoringSignalsBatcher| 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|: +or "component-auction", a [=currency tag=] |componentAuctionExpectedCurrency|, an [=origin=] +|topLevelOrigin|, and a [=real time reporting contributions map=] |realTimeContributionsMap|: -1. Let «|crossOriginTrustedScoringSignalsOrigin|, |sameOriginTrustedScoringSignals|, +1. Let «|trustedScoringSignalsAreCrossOrigin|, |sameOriginTrustedScoringSignals|, |crossOriginTrustedScoringSignals|, |scoringDataVersion|» be the result of [=fetch and - decode trusted scoring signals=] given |auctionConfig|, |generatedBid|, |decisionLogicFetcher|, - |topLevelOrigin|. + decode trusted scoring signals=] given |trustedScoringSignalsBatcher|, |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:
{{ScoringBrowserSignals/topWindowHostname}} @@ -1998,11 +2143,11 @@ or "component-auction", a [=currency tag=] |componentAuctionExpectedCurrency|, a
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 and |crossOriginTrustedScoringSignalsOrigin| is null, +
|scoringDataVersion| if it is not null and |trustedScoringSignalsAreCrossOrigin| is false, unset otherwise.
{{ScoringBrowserSignals/crossOriginDataVersion}} -
|scoringDataVersion| if it is not null and |crossOriginTrustedScoringSignalsOrigin| is not - null, unset otherwise. +
|scoringDataVersion| if it is not null and |trustedScoringSignalsAreCrossOrigin| is true, + unset otherwise.
{{ScoringBrowserSignals/adComponents}}
|generatedBid|'s [=generated bid/ad component descriptors=] [=converted to a string sequence=]
{{ScoringBrowserSignals/forDebuggingOnlyInCooldownOrLockout}} @@ -2011,9 +2156,13 @@ or "component-auction", a [=currency tag=] |componentAuctionExpectedCurrency|, a 1. Let |decisionLogicScript| be the result of [=wait for script body from a fetcher=] given |decisionLogicFetcher|. -1. If |decisionLogicScript| is falure, return. -1. Let « |scoreAdResult|, |debugWinReportUrl|, |debugLossReportUrl| » be the result of - [=evaluating a scoring script=] with |decisionLogicScript|, |adMetadata|, +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=], |sameOriginTrustedScoringSignals|, |crossOriginTrustedScoringSignals|, |browserSignals|, |directFromSellerSignalsForSeller|, and |auctionConfig|'s [=auction config/seller timeout=]. @@ -2028,9 +2177,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": @@ -2193,7 +2345,7 @@ To validate fetching response given a [=response=] |response|, null,
-To fetch WebAssembly given a [=URL=] |url|: +To fetch WebAssembly given a [=URL=] |url| and an [=environment settings object=] |settings|: 1. Let |request| be a new [=request=] with the following properties: : [=request/URL=] @@ -2202,6 +2354,8 @@ To fetch WebAssembly given a [=URL=] |url|: :: «`Accept`: `application/wasm`» : [=request/client=] :: `null` + : [=request/origin=] + :: |settings|'s [=environment settings object/origin=] : [=request/mode=] :: "`no-cors`" : [=request/referrer=] @@ -2210,6 +2364,9 @@ To fetch WebAssembly given a [=URL=] |url|: :: "`omit`" : [=request/redirect mode=] :: "`error`" + : [=request/policy container=] + :: A new [=policy container=] whose [=policy container/IP address space=] is |settings|'s + [=environment settings object/policy container=]'s [=policy container/IP address space=] 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. @@ -2234,8 +2391,8 @@ The X-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|, an [=origin=] |scriptOrigin|, -and a [=boolean=] |isBiddingSignal|: +To fetch trusted signals given a [=URL=] |url|, an [=origin=] |scriptOrigin|, a +[=policy container=] |policyContainer|, and a [=boolean=] |isBiddingSignal|: 1. Let |request| be a new [=request=] with the following properties: : [=request/URL=] @@ -2254,6 +2411,9 @@ and a [=boolean=] |isBiddingSignal|: :: "`omit`" : [=request/redirect mode=] :: "`error`" + : [=request/policy container=] + :: A new [=policy container=] whose [=policy container/IP address space=] is |policyContainer|'s + [=policy container/IP address space=] 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. @@ -2359,78 +2519,15 @@ To calculate the ad slot size query param given an [=interest group=]
- -To build trusted bidding signals url given a [=URL=] |signalsUrl|, an [=ordered set=] of -[=strings=] |keys|, an [=ordered set=] of [=strings=] |igNames|, an {{unsigned short}}-or-null -|experimentGroupId|, an [=origin=] |topLevelOrigin|, and a [=string=] |slotSizeQueryParam|: -1. Let |queryParamsList| be a new empty [=list=]. - - Note: These steps create a [=url/query=] of the form "`&=`". - E.g., "`hostname=publisher1.com&keys=key1,key2&interestGroupNames=ad+platform,name2&experimentGroupId=1234`". -

These steps don't use the [=urlencoded serializer|application/x-www-form-urlencoded - serializer=] to construct the query string because it repeats a key if it has multiple values - instead of a comma-demilited list (e.g., "`keys=key1&keys=key2`", instead of - "`keys=key1,key2`"), and it also uses a different percent encode set from the Chrome - implementation. - -1. [=list/Append=] "hostname=" to |queryParamsList|. -1. [=list/Append=] the result of [=string/UTF-8 percent-encoding=] the - [=serialization of an origin|serialized=] |topLevelOrigin| using [=component percent-encode set=] - to |queryParamsList|. -1. If |keys| is not [=set/is empty|empty=]: - 1. [=list/Append=] "`&keys=`" to |queryParamsList|. - 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with - |keys|. -1. If |igNames| is not [=set/is empty|empty=]: - 1. [=list/Append=] "`&interestGroupNames=`" to |queryParamsList|. - 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with - |igNames|. -1. If |experimentGroupId| is not null: - 1. [=list/Append=] "`&experimentGroupId=`" to |queryParamsList|. - 1. [=list/Append=] [=serialize an integer|serialized=] |experimentGroupId| to |queryParamsList|. -1. [=list/Append=] |slotSizeQueryParam| to |queryParamsList|. -1. Let |fullSignalsUrl| be |signalsUrl|. -1. Set |fullSignalsUrl|'s [=url/query=] to the result of [=string/concatenating=] |queryParamsList|. -1. return |fullSignalsUrl|. - -
- -
- -To build trusted scoring signals url given a [=URL=] |signalsUrl|, a [=list=] of -[=strings=] |renderURLs|, an [=ordered set=] of [=strings=] |adComponentRenderURLs|, an -{{unsigned short}} |experimentGroupId|, and an [=origin=] |topLevelOrigin|: - -Note: When trusted scoring signals fetches are not batched, |renderURLs|'s [=list/size=] is 1. - -1. Let |queryParamsList| be a new empty [=list=]. -1. [=list/Append=] "hostname=" to |queryParamsList|. -1. [=list/Append=] the result of [=string/UTF-8 percent-encoding=] |topLevelOrigin| using - [=component percent-encode set=] to |queryParamsList|. -1. If |renderURLs| is not [=set/is empty|empty=]: - 1. [=list/Append=] "`&renderURLs=`" to |queryParamsList|. - 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with - |renderURLs|. -1. If |adComponentRenderURLs| is not [=set/is empty|empty=]: - 1. [=list/Append=] "`&adComponentRenderURLs=`" to |queryParamsList|. - 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with - |adComponentRenderURLs|. -1. If |experimentGroupId| is not null: - 1. [=list/Append=] "`&experimentGroupId=`" to |queryParamsList|. - 1. [=list/Append=] [=serialize an integer|serialized=] |experimentGroupId| to |queryParamsList|. -1. Set |signalsUrl|'s [=url/query=] to the result of [=string/concatenating=] |queryParamsList|. -1. return |signalsUrl|. - -
- -
-To send report given a [=URL=] |url|: +To send report given a [=URL=] |url|, and an [=environment settings object=] |settings|: 1. Let |request| be a new [=request=] with the following properties: : [=request/URL=] :: |url| : [=request/client=] :: `null` + : [=request/origin=] + :: |settings|'s [=environment settings object/origin=] : [=request/mode=] :: "`no-cors`" : [=request/referrer=] @@ -2439,6 +2536,12 @@ To send report given a [=URL=] |url|: :: "`omit`" : [=request/redirect mode=] :: "`error`" + : [=request/policy container=] + :: A new [=policy container=] whose [=policy container/IP address space=] is |settings|'s + [=environment settings object/policy container=]'s [=policy container/IP address space=] + + 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). @@ -2566,7 +2669,7 @@ To report result given a [=leading bid info=] |leadingBidInfo|, a |browserSignals|["{{ReportingBrowserSignals/buyerAndSellerReportingId}}"] to |igAd|'s [=interest group ad/buyer and seller reporting ID=]. 1. Let |sellerReportingScriptFetcher| be the result of [=creating a new script fetcher=] with - |config|'s [=auction config/decision logic url=]. + |config|'s [=auction config/decision logic url=] and |global|'s [=relevant settings object=]. 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 @@ -2596,8 +2699,8 @@ To report result given a [=leading bid info=] |leadingBidInfo|, a
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 [=environment settings object=] |settings|: 1. Let |config| be |leadingBidInfo|'s [=leading bid info/auction config=]. 1. Let |winner| be |leadingBidInfo|'s [=leading bid info/leading bid=]. @@ -2637,7 +2740,7 @@ a {{ReportingBrowserSignals}} |browserSignals|, and a [=direct from seller signa 1. Otherwise, [=map/Set=] |reportWinBrowserSignals|["{{ReportWinBrowserSignals/interestGroupName}}"] to |winner|'s [=generated bid/interest group=] [=interest group/name=]. 1. Let |buyerReportingScriptFetcher| be the result of [=creating a new script fetcher=] with - |winner|'s [=generated bid/interest group=]'s [=interest group/bidding url=]. + |winner|'s [=generated bid/interest group=]'s [=interest group/bidding url=] and |settings|. 1. Let |buyerReportingScript| be the result of [=waiting for script body from a fetcher=] given |buyerReportingScriptFetcher|. 1. Let |reportFunctionName| be "`reportWin`". @@ -2662,6 +2765,35 @@ a {{ReportingBrowserSignals}} |browserSignals|, and a [=direct from seller signa :: |reportingMacroMap|
+
+To parse and validate server response given an [=auction config=] |auctionConfig|, an +[=auction config=]-or-null |topLevelAuctionConfig|, a [=global object=] |global|, +a [=list=] of [=interest groups=] |bidIgs|, and a [=list=] of [=bid debug reporting info=] +|bidDebugReportInfoList|: + +1. [=Assert=] that these steps are running [=in parallel=]. +1. [=Assert=] that |topLevelAuctionConfig| is null. + + Issue: TODO: Support multi-level auctions. + (WICG/turtledove#1254) +1. Let |requestId| be the value of |auctionConfig|'s [=auction config/server response id=]. +1. Let |requestContexts| be the value of |global|'s [=associated Document's=] [=node navigable's=] + [=traversable navigable's=] [=traversable navigable/saved Bidding and Auction request context=]. +1. If |requestContexts|[|requestId|] does not [=map/exist=], return null. +1. Let |response| be the result of deserializing |auctionConfig|'s [=auction config/server response=] + according to the Bidding and Auction Services IETF standard. + + Issue: TODO: Link deserialization to IETF standard when available. + (WICG/turtledove#1254) +1. Construct bids based on |response|. +1. Add the bids form the |response| to |bidIgs|. +1. Insert the debug reporting URLs from |response| into |bidDebugReportInfoList|. + + Issue: TODO: Spec out last few steps starting from constructing bids. + (WICG/turtledove#1254) + +
+

canLoadAdAuctionFencedFrame()

*This first introductory paragraph is non-normative.* @@ -2712,6 +2844,165 @@ The canLoadAdAuctionFencedFrame() method steps a 1. Return true. +

getInterestGroupAdAuctionData()

+ +*This first introductory paragraph is non-normative.* + +When a website or someone working on behalf of the website (e.g. a supply side platform, SSP) wants +to conduct an auction using a trusted auction server to select an advertisement to display to the +user, they can call the {{Window/navigator}}.{{Navigator/getInterestGroupAdAuctionData()}} function. +This function returns an opaque {{Uint8Array}} as the request and a request ID string. The request +can be sent to a trusted auction server through the JS [[FETCH]] API, which returns a response. +The response along with the request ID can be then passed as part of an auction configuration to +{{Window/navigator}}.{{Navigator/runAdAuction()}} to extract the results of the auction. + + +[SecureContext] +partial interface Navigator { + Promise<AdAuctionData> getInterestGroupAdAuctionData(AdAuctionDataConfig config); +}; + +dictionary AdAuctionDataConfig { + required USVString seller; + required USVString coordinatorOrigin; +}; + +dictionary AdAuctionData { + required Uint8Array request; + required USVString requestId; +}; + + +A server auction interest group is a [=struct=] with the following [=struct/items=]: +
+ : name + :: A [=string=] that uniquely defines each interest group, as in [=interest group/name=]. + : bidding signals keys + :: Null or a [=list=] of [=string=], as in [=interest group/trusted bidding signals keys=] + : user bidding signals + :: Null or a [=string=], as in [=interest group/user bidding signals=] + : ads + :: A [=list=] of [=strings=] containing the corresponding [=interest group ad/ad render IDs=] + from the [=interest group/ads=] field. + : components + :: A [=list=] of [=strings=] containing the corresponding [=interest group ad/ad render IDs=] + from the [=interest group/ad components=] field. + : browser signals + :: A [=server auction browser signals=]. +
+ +A server auction browser signals is a [=struct=] with the following [=struct/items=]: +
+ : bid count + :: A count of the number of bids for this interest group in the last 30 days. + Calculated by summing the [=interest group/bid counts=] for all days within the last 30 days. + : join count + :: A count of the number of joins for this interest group in the last 30 days. + Calculated by summing the [=interest group/join counts=]. + : recency ms + :: A [=duration=], in milliseconds, representing the [=current wall time=] at + the time this object was constructed minus the corresponding [=interest group=]'s + [=interest group/join time=], in milliseconds. + : previous wins + :: A [=sequence=]<[=server auction previous win=]> +
+ +A server auction previous win is a [=struct=] with the following [=struct/items=]: +
+ : time delta + :: A [=duration=], in milliseconds, representing the [=current wall time=] at + the time this object was constructed minus the corresponding [=previous win=]'s [=previous win/time=], in seconds. + : ad render ID + :: A [=string=] containing the [=interest group ad/ad render ID=] for the ad represented by this entry. +
+ +A server auction request context is a [=struct=] with the following [=struct/items=]: +
+ : request ID + :: A unique identifier associated with this and only this invocation of + {{Window/navigator}}.{{Navigator/getInterestGroupAdAuctionData()}}. This is + used to look-up a specific request context. + : request context + :: An opaque context used to handle the request. + + Issue: TODO: Link to the IETF Internet Draft that defines the context when it's available. + (WICG/turtledove#1254) +
+ +
+ +The getInterestGroupAdAuctionData(|config|) method steps are: + +1. Let |global| be [=this=]'s [=relevant global object=]. +1. If |global|'s [=associated Document=] is not [=allowed to use=] the "[=run-ad-auction=]" + [=policy-controlled feature=], then [=exception/throw=] a "{{NotAllowedError}}" {{DOMException}}. +1. Let |p| be [=a new promise=]. +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 |igMap| be a new [=map=] whose [=map/keys=] are [=origins=] and [=map/values=] are [=lists=]. + 1. Let |startTime| be a [=moment=] equal to the [=current wall time=]. + 1. [=list/For each=] |ig| of the [=user agent=]'s [=interest group set=]: + 1. If |ig|'s [=interest group/ads=] is null or [=list/is empty=], [=iteration/continue=]. + 1. Let |owner| be |ig|'s [=interest group/owner=]. + 1. If |igMap|[|owner|] does not [=map/exist=], then [=map/set=] |igMap|[|owner|] to a new [=list=]. + 1. Let |ads| be a new [=list=]. + 1. [=list/For each=] |ad| in |ig|'s [=interest group/ads=], [=list/append=] |ad|'s [=interest group ad/ad render ID=] to |ads|. + 1. Let |components| be a new [=list=]. + 1. [=list/For each=] |component| in |ig|'s [=interest group/ad components=], [=list/append=] |component|'s [=interest group ad/ad render ID=] to |components|. + 1. Let |prevWins| be a new [=sequence=]<[=server auction previous win=]>. + 1. [=list/For each=] |prevWin| of |ig|'s [=interest group/previous wins=] for all days within the + the last 30 days: + 1. Let |timeDelta| be |startTime| minus |prevWin|'s [=previous win/time=]. + 1. Set |timeDelta| to 0 if |timeDelta| is negative, |timeDelta|'s nearest second (rounding down) + otherwise. + 1. Let |serverPrevWin| be a new [=server auction previous win=] with the following [=struct/items=]: + : [=server auction previous win/time delta=] + :: |timeDelta| + : [=server auction previous win/ad render ID=] + :: the value of the [=interest group ad/ad render ID=] field in |prevWin|'s [=previous win/ad=], or the empty string if not present + 1. [=list/Append=] |serverPrevWin| to |prevWins|. + 1. Let |browserSignals| be a new [=server auction browser signals=] with the following [=struct/items=]: + : [=server auction browser signals/bid count=] + :: the sum of |ig|'s [=interest group/bid counts=] with a bid day within the last 30 days + : [=server auction browser signals/join count=] + :: the sum of |ig|'s [=interest group/join counts=] with a join day within the last 30 days + : [=server auction browser signals/recency ms=] + :: the [=current wall time=] minus |ig|'s [=interest group/join time=] in millseconds + : [=server auction browser signals/previous wins=] + :: |prevWins| + 1. Let |serverIg| be a new [=server auction interest group=] with the following [=struct/items=]: + : [=server auction interest group/name=] + :: |ig|'s [=interest group/name=] + : [=server auction interest group/bidding signals keys=] + :: |ig|'s [=interest group/trusted bidding signals keys=] + : [=server auction interest group/user bidding signals=] + :: |ig|'s [=interest group/user bidding signals=] + : [=server auction interest group/ads=] + :: |ads| + : [=server auction interest group/components=] + :: |components| + : [=server auction interest group/browser signals=] + :: |browserSignals| + 1. [=list/Append=] |serverIg| to |igMap|[|owner|]. + 1. Let |result| be a new {{AdAuctionData}}. + 1. Let |requestId| be the [=string representation=] of a [=version 4 UUID=]. + 1. Set |result|'s {{AdAuctionData/requestId}} field to |requestId|. + 1. Let |context| be the result of serializing |igMap| using |config| into |result|'s + {{AdAuctionData/request}} field. + + Issue: TODO: Link to the IETF Internet Draft that defines the serialization when it's available. + (WICG/turtledove#1254) + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |global|, to + resolve |p| with |result|. + 1. Let |requestContext| be a new [=server auction request context=]. + 1. Set |requestContext|'s [=server auction request context/request ID=] field to the value of |result|'s {{AdAuctionData/requestId}} field. + 1. Set |requestContext|'s [=server auction request context/request context=] field to |context|. + 1. [=map/Set=] |global|'s [=associated Document's=] [=node navigable's=] + [=traversable navigable's=] [=traversable navigable/saved Bidding and Auction request context=][|requestId|] to |requestContext|. +1. Return p. + +
+ # Reporting # {#reporting} ## {{InterestGroupBiddingAndScoringScriptRunnerGlobalScope/forDebuggingOnly}} ## {#for-debugging-only-header} @@ -2871,6 +3162,223 @@ and [=map/values=] are [=moments=] at which the cool down for the origin key exp 1. Return |canSendAfterSampled|.
+## {{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()`. + +
+

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 [=environment settings object=] |settings|: + + 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=] + :: |settings|'s [=environment settings object/origin=] + : [=request/mode=] + :: "`no-cors`" + : [=request/referrer=] + :: "`no-referrer`" + : [=request/credentials mode=] + :: "`omit`" + : [=request/redirect mode=] + :: "`error`" + : [=request/policy container=] + :: A new [=policy container=] whose [=policy container/IP address space=] is |settings|'s + [=environment settings object/policy container=]'s [=policy container/IP address space=] + + 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 [=environment settings object=] |settings|: + + 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 |settings|. + + 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 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|. +
+ # Additional Bids and Negative Targeting # {#additional-bids-and-negative-targeting} ## createAuctionNonce() ## {#create-auction-nonce} @@ -3303,70 +3811,80 @@ might choose to require a [=k-anonymity=] threshold of fifty users over a seven will maintain the count over the chosen duration and compare the count to the chosen [=k-anonymity=] threshold when responding to [=query k-anonymity count=]. +The [=user agent=] must maintain a k-anonymity cache as a [=map=] whose [=map/keys=] are +[=SHA-256=] hashes of the [=k-anonymity keys=] for all of the [=interest group/ads=] and [=interest group/ad components=] +in the [=user agent=]'s [=interest group set=] and whose [=map/values=] are [=k-anonymity records=]. +This allows the browser to rerun portions of an auction without incurring the delay (and added side channels) +from querying the server during an auction. +
- To query k-anonymity count given a |hashCode|: + To query k-anonymity count given a [=SHA-256=] |hashCode|: 1. If the [=k-anonymity server=] has recorded at least [=k-anonymity threshold=] users seeing |hashCode| over the last [=k-anonymity duration=], return true. - Otherwise return false. + Otherwise, return false. 1. Return true if it is above the threshold, otherwise return false.
- To query ad k-anonymity count given an [=interest group=] |ig| and a [=URL=] |ad|: - 1. Let |keyString| be the [=string/concatenation=] of the following strings separated with U+000A LF: - 1. "AdBid" - 1. the [=serialization of an origin|serialization=] of |ig|'s [=interest group/owner=] - 1. the [=URL serializer|serialization=] of |ig|'s [=interest group/bidding url=] - 1. the [=URL serializer|serialization=] of |ad|. - 1. Let |keyHash| be the [=SHA-256=] hash of the [=ASCII encoding=] of |keyString|. + To query k-anonymity cache given a [=SHA-256=] |hashCode|: + 1. If the [=user agent=]'s [=k-anonymity cache=] does not [=map/contain=] |hashCode|, then return false. + 1. Let |record| be the [=user agent=]'s [=k-anonymity cache=][|hashCode|]. + 1. If the difference between [=current wall time=] and |record|'s [=k-anonymity record/timestamp=] is more than 7 days then return false. + 1. Return |record|'s [=k-anonymity record/is k-anonymous=]. +
- 1. Return the result of [=query k-anonymity count|querying the k-anonymity count=] given |keyHash|. +
+ To compute the key hash of ad given an [=interest group=] |ig| and an [=interest group ad=] |igAd|: + 1. Let |keyString| be the [=k-anonymity key=] formed from the [=string/concatenation=] of the following strings separated with U+000A LF: + * "AdBid" + * the [=serialization of an origin|serialization=] of |ig|'s [=interest group/owner=] + * the [=URL serializer|serialization=] of |ig|'s [=interest group/bidding url=] + * the [=URL serializer|serialization=] of |igAd|'s [=interest group ad/render url=]. + 1. Return the [=SHA-256=] hash of the [=ASCII encoding=] of |keyString|.
To compute the key hash of reporting ID given an [=interest group=] |ig| and an [=interest group ad=] |igAd|: - 1. Let |keyString| be the [=string/concatenation=] of the following strings separated with U+000A (LF): + 1. Let |keyString| be a [=k-anonymity key=] formed from the [=string/concatenation=] of the following strings separated with U+000A (LF): - 1. "NameReport" - 1. the [=serialization of an origin|serialization=] of |ig|'s [=interest group/owner=] - 1. the [=URL serializer|serialization=] of |ig|'s [=interest group/bidding url=] - 1. the [=URL serializer|serialization=] of |igAd|'s [=interest group ad/render url=] - 1. If |igAd|'s [=interest group ad/buyer and seller reporting ID=] + * "NameReport" + * the [=serialization of an origin|serialization=] of |ig|'s [=interest group/owner=] + * the [=URL serializer|serialization=] of |ig|'s [=interest group/bidding url=] + * the [=URL serializer|serialization=] of |igAd|'s [=interest group ad/render url=] + * If |igAd|'s [=interest group ad/buyer and seller reporting ID=] [=map/exists=]: - 1. "BuyerAndSellerReportingId" - 1. |igAd|'s [=interest group ad/buyer and seller reporting ID=] - 1. Otherwise, if |igAd|'s [=interest group ad/buyer reporting ID=] + * "BuyerAndSellerReportingId" + * |igAd|'s [=interest group ad/buyer and seller reporting ID=] + * Otherwise, if |igAd|'s [=interest group ad/buyer reporting ID=] [=map/exists=]: - 1. "BuyerReportingId" - 1. |igAd|'s [=interest group ad/buyer reporting ID=] - 1. Otherwise: - 1. "IgName" - 1. |ig|'s [=interest group/name=]. - 1. Return the [=SHA-256=] hash of the [=ASCII encoding=] of |keyString|. + * "BuyerReportingId" + * |igAd|'s [=interest group ad/buyer reporting ID=] + * Otherwise: + * "IgName" + * |ig|'s [=interest group/name=]. + 1. Return the [=SHA-256=] hash of the [=ASCII encoding=] of |keyString|.
- To query component ad k-anonymity count given a [=URL=] |ad|: - 1. Let |keyString| be the [=string/concatenation=] of the following strings separated with U+000A LF: + To compute the key hash of component ad given an [=interest group ad=] |igAd|: + 1. Let |keyString| be the [=string/concatenation=] of the following strings separated with U+000A LF: 1. "ComponentBid" - 1. the [=URL serializer|serialization=] of |ad|. - 1. Let |keyHash| be the [=SHA-256=] hash of the [=ASCII encoding=] of |keyString|. - - 1. Return the result of [=query k-anonymity count|querying the k-anonymity count=] given |keyHash|. + 1. the [=URL serializer|serialization=] of |igAd|. + 1. Return the [=SHA-256=] hash of the [=ASCII encoding=] of |keyString|.
To query generated bid k-anonymity count given a [=generated bid=] |bid|: - 1. If [=query ad k-anonymity count=] given |bid|'s [=generated bid/ad descriptor=]'s - [=ad descriptor/url=] returns false, return false. + 1. Compute the |adHashCode| following [=compute the key hash of ad=] with the |bid|'s [=generated bid/interest group=] and |bid|'s [=generated bid/ad descriptor=]. + 1. If [=query k-anonymity cache=] for |adHashCode| returns false, return false. 1. If |bid|'s [=generated bid/ad component descriptors=] is not null: 1. [=set/For each=] |adComponentDescriptor| in |bid|'s [=generated bid/ad component descriptors=]: - 1. If [=query component ad k-anonymity count=] given |adComponentDescriptor|'s - [=ad descriptor/url=] returns false, return false. + 1. Compute the |componentAdHashCode| by getting the result of [=compute the key hash of component ad=] with |adComponentDescriptor|'s + [=ad descriptor/url=]. + 1. If [=query k-anonymity cache=] for |componentAdHashCode| returns false, return false. 1. Return true. -
@@ -3376,6 +3894,26 @@ threshold when responding to [=query k-anonymity count=]. 1. Return the result of [=query k-anonymity count|querying the k-anonymity count=] given |keyHash|.
+
+ To update k-anonymity cache for key given a [=SHA-256=] |hashCode|: + 1. Let |record| be a new [=k-anonymity record=]. + 1. Set |record|'s [=k-anonymity record/timestamp=] field to the [=current wall time=]. + 1. Set |record|'s [=k-anonymity record/is k-anonymous=] field to the result of executing [=query k-anonymity count=] for |hashCode|. + 1. [=map/Set=] |record|[|hashCode|] to |record|. +
+ +
+ To update k-anonymity cache for interest group given an [=interest group=] |ig|: + 1. [=list/For each=] |igAd| of |ig|'s [=interest group/ads=]: + 1. Compute the |adHashCode| following [=compute the key hash of ad=] for |ig| and |igAd|. + 1. Run [=update k-anonymity cache for key=] on |adHashCode|. + 1. Compute the |adReportingHashCode| following [=compute the key hash of reporting ID=]. + 1. Run [=update k-anonymity cache for key=] on |adReportingHashCode|. + 1. [=list/For each=] |componentAd| of |ig|'s [=interest group/ad components=]: + 1. Compute the |componentAdHashCode| following [=compute the key hash of component ad=] for |componentAd|. + 1. Run [=update k-anonymity cache for key=] on |componentAdHashCode|. +
+
To increment k-anonymity count given a |hashCode|: 1. Ask the [=k-anonymity server=] to record that this [=user agent=] has seen |hashCode|. @@ -3533,7 +4071,9 @@ of the following global objects: |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}}. @@ -3598,7 +4138,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. @@ -3610,8 +4150,14 @@ 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|).
@@ -3631,17 +4177,26 @@ of the following global objects: [=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|, |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| ».
@@ -3747,19 +4302,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}}.
@@ -3812,6 +4379,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}
@@ -3979,6 +4585,8 @@ To convert GenerateBidOutput to generated bid given a {{GenerateBidOu
   1. Let |dimension| be the result of parsing |dimensionString| using the
     [=rules for parsing floating-point number values=].
   1. If |dimension| is an error, then return null as the dimension and the empty string as the dimension unit.
+  1. If |dimension| is less than 0, then return null as the dimension and the empty string as the
+     dimension unit.
   1. [=Collect a sequence of code points=] that are [=ASCII lower alpha=], given |position|. Let
     that be |dimensionUnit|.
   1. If |position| is not past the end of |input|, then return null as the dimension and the empty
@@ -4017,11 +4625,16 @@ To convert GenerateBidOutput to generated bid given a {{GenerateBidOu
 
   1. Let |adUrl| be |adDescriptor|'s [=ad descriptor/url=].
   1. If |adUrl|'s [=url/scheme=] is not "`https`", return null.
-  1. TODO: Need to check [=ad descriptor/size=] as well.
-  1. If |isComponent|, [=list/for each=] |ad| in |ig|'s [=interest group/ad components=]:
-    1. If |ad|'s [=interest group ad/render url=] equals |adUrl|, return |ad|.
-  1. Otherwise, [=list/for each=] |ad| in |ig|'s [=interest group/ads=]:
-    1. If |ad|'s [=interest group ad/render url=] equals |adUrl|, return |ad|.
+  1. Let |adSize| be |adDescriptor|'s [=ad descriptor/size=].
+  1. Let |adList| be |ig|'s [=interest group/ad components=] if |isComponent|, otherwise |ig|'s
+     [=interest group/ads=].
+  1. [=list/For each=] |ad| in |adList|:
+    1. If |ad|'s [=interest group ad/render url=] does not equal |adUrl|, [=iteration/continue=].
+    1. If |ad|'s [=interest group ad/size group=] is null, return |ad|.
+    1. If |adSize| is null, return |ad|.
+    1. [=list/For each=] |igSize| in (|ig|'s [=interest group/size groups=])[|ad|'s
+       [=interest group ad/size group=]]:
+      1. If |igSize| equals |adSize|, return |ad|.
   1. Return null.
 
@@ -4184,13 +4797,14 @@ partial interface Navigator { The updateAdInterestGroups() method steps are: -1. [=In parallel=], run [=interest group update=] with - « [=relevant settings object=]'s [=environment/top-level origin=] » +1. Let |settings| be [=this=]'s [=relevant settings object=]. +1. [=In parallel=], run [=interest group update=] with « |settings|'s [=environment/top-level origin=] », + and |settings|'s [=environment settings object/policy container=].
To update interest groups given a [=list=] of [=origins=] - |owners|: + |owners|, and a [=policy container=] |policyContainer|: Implementations can consider aborting all updating if updating has been running for too long. This can avoid continuing to reveal coarse IP location information to update servers long after @@ -4204,6 +4818,7 @@ navigating to another page. Some implementations, such as Chromium, have chosen Note: Implementations can consider loading only a portion of these interest groups at a time to avoid issuing too many requests at once. 1. Let |ig| be a deep copy of |originalInterestGroup|. + 1. Run [=update k-anonymity cache for interest group=] for |ig|. 1. Let |request| be a new [=request=] with the following properties: : [=request/URL=] :: |ig|'s [=interest group/update url=] @@ -4211,6 +4826,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=] @@ -4219,6 +4836,9 @@ navigating to another page. Some implementations, such as Chromium, have chosen :: "`omit`" : [=request/redirect mode=] :: "`error`" + : [=request/policy container=] + :: A new [=policy container=] whose [=policy container/IP address space=] is |policyContainer|'s + [=policy container/IP address space=] 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. @@ -4370,6 +4990,30 @@ navigating to another page. Some implementations, such as Chromium, have chosen Issue: Serializing an Infra value to JSON bytes expects to be called within a valid ES realm. See infra/625 +
"`adSizes`" +
+ 1. Let |adSizes| be a new [=map=] whose [=map/keys=] are [=strings=] and [=map/values=] are + [=ad sizes=]. + 1. [=map/For each=] |sizeName| → |size| of |value|: + 1. If |sizeName| is "", jump to the step labeled Abort update. + 1. Let |adSize| be the result from running [=parse an AdRender ad size=] with |size|. + 1. If |adSize| is null, jump to the step labeled Abort update. + 1. [=map/Set=] |adSizes|[|sizeName|] to |adSize|. + 1. Set |ig|'s [=interest group/ad sizes=] to |adSizes|. + +
"`sizeGroups`" +
+ 1. Let |adSizes| be |ig|'s [=interest group/ad sizes=]. + 1. Let |sizeGroups| be a new [=map=] whose [=map/keys=] are [=strings=] and [=map/values=] + are [=strings=]. + 1. [=map/For each=] |sizeGroupName| → |sizeList| of |value|: + 1. If |sizeGroupName| is "", jump to the step labeled Abort update. + 1. [=list/For each=] |sizeName| of |sizeList|: + 1. If |sizeName| is "" or |adSizes|[|sizeName|] does not [=map/exist=], + jump to the step labeled Abort update. + 1. [=map/Set=] |sizeGroups|[|sizeGroupName|] to |sizeList|. + 1. Set |ig|'s [=interest group/size groups=] to |sizeGroups|. +
"`ads`"
"`adComponents`"
@@ -4399,6 +5043,13 @@ navigating to another page. Some implementations, such as Chromium, have chosen * |renderURL| [=url/scheme=] is not "`https`"; * |renderURL| [=includes credentials=]. 1. Set |igAd|'s [=interest group ad/render url=] to |renderURL|. + 1. If |ad|["{{AuctionAd/sizeGroup}}"] [=map/exists=]: + 1. Let |sizeGroup| be |ad|["{{AuctionAd/sizeGroup}}}]. + 1. Jump to the step labeled Abort update if none of + the following conditions hold: + * |adSizes|[|sizeGroup|] [=map/exists=]. + * |sizeGroups|[|sizeGroup|] [=map/exists=]. + 1. Set |igAd|'s [=interest group ad/size group=] to |sizeGroup|. 1. If |ad|["{{AuctionAd/metadata}}"] [=map/exists=], then let |igAd|'s [=interest group ad/metadata=] be the result of [=serializing a JavaScript value to a JSON string=], given |ad|["{{AuctionAd/metadata}}"]. @@ -4427,7 +5078,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 @@ -4461,7 +5112,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] @@ -4483,11 +5136,13 @@ The <dfn for=ProtectedAudience method>queryFeatureSupport(feature)</dfn> method : "adComponentsLimit" :: 40 : "deprecatedRenderURLReplacements" - :: true + :: true : "permitCrossOriginTrustedSignals" - :: true + :: true + : "realTimeReporting" + :: true : "reportingTimeout" - :: true + :: true 1. If |feature| is "*", then return |featuresTable|. 1. If |featuresTable|[|feature|] [=map/exists=], then return |featuresTable|[|feature|]. 1. Return `undefined`. @@ -4542,6 +5197,7 @@ The <dfn for="interest group">estimated size</dfn> of an [=interest group=] |ig| 1. If |ig|'s [=interest group/ads=] is not null, [=list/for each=] |ad| of it: 1. The [=string/length=] of the [=URL serializer|serialization=] of |ad|'s [=interest group ad/render url=]. + 1. The [=string/length=] of |ad|'s [=interest group ad/size group=] if the field is not null. 1. The [=string/length=] of |ad|'s [=interest group ad/metadata=] if the field is not null. 1. The [=string/length=] of |ad|'s [=interest group ad/buyer reporting ID=] if the field is not null. 1. The [=string/length=] of |ad|'s [=interest group ad/buyer and seller reporting ID=] if the @@ -4552,7 +5208,18 @@ The <dfn for="interest group">estimated size</dfn> of an [=interest group=] |ig| 1. If |ig|'s [=interest group/ad components=] is not null, [=list/for each=] |ad| of it: 1. The [=string/length=] of the [=URL serializer|serialization=] of |ad|'s [=interest group ad/render url=]. + 1. The [=string/length=] of |ad|'s [=interest group ad/size group=] if the field is not null. 1. The [=string/length=] of |ad|'s [=interest group ad/metadata=] if the field is not null. +1. If |ig|'s [=interest group/ad sizes=] is not null, [=map/for each=] |sizeName| → |size| of it: + 1. The sum of the [=string/length=] of |sizeName| and 18 (representing the fixed length of |size|). + + Note: 10 represents the size of 2 doubles for [=ad size/width=] and [=ad size/height=] (each 8 + bytes), and 2 bytes for enums for [=ad size/width units=] and [=ad size/height units=] (each 1 byte). +1. If |ig|'s [=interest group/size groups=] is not null, [=map/for each=] |sizeGroupName| → |sizeList| + of it: + 1. The sum of the [=string/length=] of |sizeGroupName| and the following: + 1. [=list/For each=] |sizeName| of |sizeList|: + 1. The [=string/length=] of |sizeName|. 1. If |ig|'s [=interest group/additional bid key=] is not null: 1. 32, which is its size (number of bytes). @@ -4643,6 +5310,14 @@ Issue: This would ideally be replaced by a more descriptive algorithm in Infra. 1. Return [=ASCII encoded=] |uuidStr|. </div> +<div algorithm> + To <dfn>insert entries to map</dfn> 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|. +</div> + # Permissions Policy Integration # {#permissions-policy-integration} @@ -4656,8 +5331,8 @@ Issue: Move from "`*`" to "`self`" <div algorithm> -To <dfn>check interest group permissions</dfn> given an [=origin=] |ownerOrigin|, an [=origin=] -|frameOrigin|, and an enum |joinOrLeave| which is "`join`" or "`leave`": +To <dfn>check interest group permissions</dfn> given an [=origin=] |ownerOrigin|, an +[=environment settings object=] |settings|, and an enum |joinOrLeave| which is "`join`" or "`leave`": 1. If |ownerOrigin| is [=same origin=] with |frameOrigin|, then return true. 1. Let |encodedFrameOrigin| be the result of [=string/UTF-8 percent-encoding=] the [=serialization of an origin|serialized=] |frameOrigin| using [=component percent-encode set=]. @@ -4678,9 +5353,9 @@ To <dfn>check interest group permissions</dfn> given an [=origin=] |ownerOrigin| : [=request/header list=] :: «`Accept`: `application/json`» : [=request/client=] - :: `null` + :: |settings| : [=request/origin=] - :: |frameOrigin| + :: "`client`" : [=request/mode=] :: "`cors`" : [=request/referrer=] @@ -4689,9 +5364,11 @@ To <dfn>check interest group permissions</dfn> given an [=origin=] |ownerOrigin| :: "`omit`" : [=request/redirect mode=] :: "`error`" + : [=request/service-workers mode=] + :: `none` + : [=request/policy container=] + :: "`client`" - 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. 1. Let |resource| be null. 1. [=Fetch=] |request| with [=fetch/useParallelQueue=] set to true, and [=fetch/processResponseConsumeBody=] set to the following steps given a [=response=] |response| @@ -4723,6 +5400,9 @@ prevents a leak of the user's ad interest group membership to the server. # Fetch Patch for Auction Headers # {#fetch-patch-for-auction-headers} + Issue: TODO: Handle Bidding and Auction Server header. + (<a href="https://github.com/WICG/turtledove/issues/1254">WICG/turtledove#1254</a>) + This section specifies a manner by which some data, including [=additional bids=] and [=direct from seller signals=], may be provided to auctions such that the data is only used within their intended auction. @@ -4761,6 +5441,11 @@ with the {{RequestInit/adAuctionHeaders}} option set to `true`, or during an <a spec="html" lt="navigate an iframe or frame">iframe navigation</a> request with the <{iframe/adauctionheaders}> <a spec=html>content attribute</a> set to `true`, as described in the [:Ad-Auction-Additional-Bid:] header description. + +Each [=traversable navigable=] has a <dfn for="traversable navigable">saved Bidding +and Auction request context</dfn>, which is a [=map=] whose [=map/keys=] are +the [=string representation=] of a [=version 4 UUID=] and whose [=map/values=] +are [=server auction request contexts=]. </div> <div algorithm="fetch capture adAuctionHeaders boolean patch"> @@ -4971,10 +5656,8 @@ To <dfn>handle ad auction signals header value</dfn> given a [=byte sequence=] | # Structures # {#structures} <xmp class="idl"> -dictionary PreviousWin { - required long long timeDelta; - required DOMString adJSON; -}; +typedef (long long or AuctionAd) PreviousWinElement; +typedef sequence<PreviousWinElement> PreviousWin; dictionary BiddingBrowserSignals { required DOMString topWindowHostname; @@ -5011,7 +5694,7 @@ dictionary ScoringBrowserSignals { <div algorithm> To <dfn>convert an ad size to a map</dfn> 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|), @@ -5247,6 +5930,13 @@ An <dfn>interest group</dfn> is a [=struct=] with the following [=struct/items=] :: Null or a [=list=] of [=interest group ad=]. Contains various ad components (or "products") that can be used to construct ads composed of multiple pieces — a top-level ad template "container" which includes some slots that can be filled in with specific "products". + : <dfn>ad sizes</dfn> + :: Null or a [=map=] whose [=map/keys=] are [=strings=] and [=map/values=] are [=ad sizes=]. + Contains named sizes (width and height) that can be bound to ads to specify their render size. + : <dfn>size groups</dfn> + :: Null or a [=map=] whose [=map/keys=] are [=strings=] and [=map/values=] are [=lists=] of + [=strings=]. Contains named collections of ad sizes that can be used to declare a bundle of + possible sizes which all apply to one ad url. : <dfn>additional bid key</dfn> :: Null or a [=byte sequence=] of length 32. Must be null if [=interest group/ads=] or [=interest group/update url=] is not null. The Ed25519 public key (a 256-bit EdDSA public key) @@ -5289,6 +5979,8 @@ An <dfn>interest group ad</dfn> is a [=struct=] with the following [=struct/item :: A [=URL=]. If this ad wins the auction, this URL (or a [=urn uuid=] that maps to this URL) will be returned by {{Navigator/runAdAuction()}}. This URL is intended to be loaded into an ad <{iframe}> (or a <{fencedframe}>). + : <dfn>size group</dfn> + :: Null or a [=string=], initially null. The name of the size group (collection of render sizes) bound to this ad. : <dfn>metadata</dfn> :: Null or a [=string=]. Extra arbitary information about this ad, passed to `generateBid()`. : <dfn>buyer reporting ID</dfn> @@ -5305,6 +5997,9 @@ An <dfn>interest group ad</dfn> is a [=struct=] with the following [=struct/item with registered macros. Each origin's [=origin/scheme=] must be "`https`" and each origin must be <a href="https://github.com/privacysandbox/attestation">enrolled</a>. Only meaningful in [=interest group/ads=], but ignored in [=interest group/ad components=]. + : <dfn>ad render ID</dfn> + :: A [=string=] containing up to 12 [=ASCII bytes=] uniquely identifying this ad. Sent instead + of the full [=interest group ad=] for auctions executed on a server. </dl> A <dfn>previous win</dfn> is the [=interest group=]'s auction win history, to allow on-device @@ -5313,8 +6008,9 @@ frequency capping. It's a [=struct=] with the following [=struct/items=]: <dl dfn-for="previous win"> : <dfn>time</dfn> :: A [=moment=]. Approximate time the [=interest group=] won an auction. - : <dfn>ad json</dfn> - :: A [=string=]. A JSON serialized object corresponding to the ad that won the auction. + : <dfn>ad</dfn> + :: An [=interest group ad=]. The ad that won the auction. + [=struct/Items=] except [=interest group ad/render url=], [=interest group ad/metadata=] and [=interest group ad/ad render ID=] are excluded. </dl> A <dfn>seller capability</dfn> is a permission granted by an [=interest group=] to sellers. It's @@ -5361,8 +6057,9 @@ An <dfn>auction config</dfn> is a [=struct=] with the following [=struct/items=] :: An [=origin=]. The origin of the seller running the ad auction. The [=origin/scheme=] must be "`https`". : <dfn>decision logic url</dfn> - :: A [=URL=]. - The URL to fetch the seller's JavaScript from. + :: Null or a [=URL=]. + The URL to fetch the seller's JavaScript from. May be null when a + [=auction config/server response=] is specified, otherwise is required. <p class="note"> The [=auction config/decision logic url=]'s [=origin=] will always be [=same origin=] with [=auction config/seller=]. @@ -5524,6 +6221,24 @@ An <dfn>auction config</dfn> 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=]. + : <dfn>seller real time reporting config</dfn> + :: 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. + : <dfn>per buyer real time reporting config</dfn> + :: 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. + : <dfn>server response</dfn> + :: Null or a {{Promise}} or a {{Uint8Array}} containing an encrypted response from the trusted auction server. + : <dfn>server response id</dfn> + :: Null or a [=version 4 UUID=] + A UUID used to match a request from + {{Window/navigator}}.{{Navigator/getInterestGroupAdAuctionData()}} to the + encrypted response stored in [=auction config=]'s [=auction config/server response=] field. + Must be null when [=auction config=]'s [=auction config/server response=] is null, + and non-null otherwise. </dl> <div algorithm> @@ -5539,7 +6254,8 @@ To <dfn>wait until configuration input promises resolve</dfn> 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. </div> @@ -5617,12 +6333,13 @@ headers. It's a [=struct=] with the following [=struct/items=]: </dl> <div algorithm> -To <dfn>create a new script fetcher</dfn> given a [=URL=] |url|: +To <dfn>create a new script fetcher</dfn> given a [=URL=] |url| and an [=environment settings object=] +|settings|: 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| and |fetcher|. + 1. [=Fetch script=] given |url|, |settings| and |fetcher|. 1. Return |fetcher|. </div> @@ -5637,7 +6354,7 @@ To <dfn>wait for script body from a fetcher</dfn> given a [=script fetcher=] |fe To <dfn>wait for cross origin trusted scoring signals authorization from a fetcher</dfn> given a [=script fetcher=] |fetcher|: - 1. Wait until |fetcher|'s [=script fetcher/origins authorized for cross origin trusted signals=]. + 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=]. </div> @@ -5662,7 +6379,8 @@ To <dfn>parse allowed trusted scoring signals origins</dfn> given a [=header lis </div> <div algorithm> -To <dfn>fetch script</dfn> given a [=URL=] |url| and a [=script fetcher=] |fetcher|: +To <dfn>fetch script</dfn> given a [=URL=] |url|, an [=environment settings object=] |settings|, and +a [=script fetcher=] |fetcher|: 1. Let |request| be a new [=request=] with the following properties: : [=request/URL=] :: |url| @@ -5670,14 +6388,19 @@ To <dfn>fetch script</dfn> given a [=URL=] |url| and a [=script fetcher=] |fetch :: «`Accept`: `text/javascript`» : [=request/client=] :: `null` + : [=request/origin=] + :: |settings|'s [=environment settings object/origin=] : [=request/mode=] :: "`no-cors`" : [=request/referrer=] - :: "`no-referrer`" + :: "`no-referrer`" : [=request/credentials mode=] :: "`omit`" : [=request/redirect mode=] - :: "`error`" + :: "`error`" + : [=request/policy container=] + :: A new [=policy container=] whose [=policy container/IP address space=] is |settings|'s + [=environment settings object/policy container=]'s [=policy container/IP address space=] 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. @@ -5702,10 +6425,10 @@ To <dfn>fetch script</dfn> given a [=URL=] |url| and a [=script fetcher=] |fetch 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. Otherwise, set |fetcher|'s [=script fetcher/script body=] to |responseBody|. 1. Let |failureSteps| be a set of steps that take an [=exception=] <var ignore>e</var>, and perform the following: - 1. Set set |fetcher|'s [=script fetcher/script body=] to failure. + 1. Set |fetcher|'s [=script fetcher/script body=] to failure. 1. [=ReadableStreamDefaultReader/Read all bytes=] from |bodyReader|, given |successSteps| and |failureSteps|. </div> @@ -5724,8 +6447,9 @@ into smaller number of fetches. It's a [=struct=] with the following [=struct/it [=map/values=] are [=bidding signals per interest group data=]. : <dfn>no signals flags</dfn> :: An [=ordered map=] whose [=map/keys=] are [=interest group/name=] [=strings=] and whose - [=map/values=] are {{boolean}}s. This is set if given interest group did not request any - trusted bidding signals keys or if its trusted signals fetch failed. + [=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. : <dfn>data versions</dfn> :: 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 @@ -5775,12 +6499,48 @@ To <dfn>append to a bidding signals per-interest group data map</dfn> given an [ </div> +<div algorithm> + +To <dfn>build trusted bidding signals url</dfn> given a [=URL=] |signalsUrl|, an [=ordered set=] of +[=strings=] |keys|, an [=ordered set=] of [=strings=] |igNames|, an {{unsigned short}}-or-null +|experimentGroupId|, an [=origin=] |topLevelOrigin|, and a [=string=] |slotSizeQueryParam|: +1. Let |queryParamsList| be a new empty [=list=]. + + Note: These steps create a [=url/query=] of the form "`&<name>=<values in comma-delimited list>`". + E.g., "`hostname=publisher1.com&keys=key1,key2&interestGroupNames=ad+platform,name2&experimentGroupId=1234`". + <br><br>These steps don't use the [=urlencoded serializer|application/x-www-form-urlencoded + serializer=] to construct the query string because it repeats a key if it has multiple values + instead of a comma-demilited list (e.g., "`keys=key1&keys=key2`", instead of + "`keys=key1,key2`"), and it also uses a different percent encode set from the Chrome + implementation. + +1. [=list/Append=] "hostname=" to |queryParamsList|. +1. [=list/Append=] the result of [=string/UTF-8 percent-encoding=] the + [=serialization of an origin|serialized=] |topLevelOrigin| using [=component percent-encode set=] + to |queryParamsList|. +1. If |keys| is not [=set/is empty|empty=]: + 1. [=list/Append=] "`&keys=`" to |queryParamsList|. + 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with + |keys|. +1. If |igNames| is not [=set/is empty|empty=]: + 1. [=list/Append=] "`&interestGroupNames=`" to |queryParamsList|. + 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with + |igNames|. +1. If |experimentGroupId| is not null: + 1. [=list/Append=] "`&experimentGroupId=`" to |queryParamsList|. + 1. [=list/Append=] [=serialize an integer|serialized=] |experimentGroupId| to |queryParamsList|. +1. [=list/Append=] |slotSizeQueryParam| to |queryParamsList|. +1. Let |fullSignalsUrl| be |signalsUrl|. +1. Set |fullSignalsUrl|'s [=url/query=] to the result of [=string/concatenating=] |queryParamsList|. +1. Return |fullSignalsUrl|. + +</div> <div algorithm> To <dfn>fetch the current outstanding trusted signals batch</dfn> 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|: +an [=origin=] |scriptOrigin|, an {{unsigned short}}-or-null |experimentGroupId|, an [=origin=] +|topLevelOrigin|, a [=string=] |slotSizeQueryParam|, and a [=policy container=] |policyContainer|: 1. If |signalsUrl| is null, return. 1. Let |biddingSignalsUrl| be the result of [=building trusted bidding signals url=] with @@ -5788,7 +6548,8 @@ an [=origin=] |topLevelOrigin|, and a [=string=] |slotSizeQueryParam|: |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. + result of [=fetching trusted signals=] with |biddingSignalsUrl|, |scriptOrigin|, + |policyContainer| 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 @@ -5800,27 +6561,27 @@ an [=origin=] |topLevelOrigin|, and a [=string=] |slotSizeQueryParam|: |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 + 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 true. + [=trusted bidding signals batcher/no signals flags=][|igName|] to "fetch-failed". </div> <div algorithm> To <dfn>batch or fetch trusted bidding signals</dfn> 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|: +|trustedBiddingSignalsBatcher|, [=interest group=] |ig|, a [=URL=] |signalsUrl|, an [=origin=] +|scriptOrigin|, an {{unsigned short}}-or-null |experimentGroupId|, an [=origin=] |topLevelOrigin|, a +[=string=] |slotSizeQueryParam|, and a [=policy container=] |policyContainer|: + 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=] - [|ig|'s [=interest group/name=]] to true. + [|igName|] to "no-fetch". 1. Return. 1. [=map/Set=] |trustedBiddingSignalsBatcher|'s - [=trusted bidding signals batcher/no signals flags=][|ig|'s [=interest group/name=]] to true - if |ig|'s [=interest group/trusted bidding signals keys=] is null or [=map/is empty=]; - otherwise set it to false. + [=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 @@ -5838,7 +6599,7 @@ an [=origin=] |topLevelOrigin|, and a [=string=] |slotSizeQueryParam|: [=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=] |ig|'s [=interest group/name=] to |putativeIgNames|. + 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|. @@ -5850,19 +6611,240 @@ an [=origin=] |topLevelOrigin|, and a [=string=] |slotSizeQueryParam|: |putativeLengthLimit|. 1. Otherwise: 1. [=Fetch the current outstanding trusted signals batch=] given |trustedBiddingSignalsBatcher|, - |signalsUrl|, |scriptOrigin|, |experimentGroupId|, |topLevelOrigin|, |slotSizeQueryParam|. + |signalsUrl|, |scriptOrigin|, |experimentGroupId|, |topLevelOrigin|, |slotSizeQueryParam|, and + |policyContainer|. 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 - « |ig|'s [=interest group/name=] ». + « |igName| ». 1. Set |trustedBiddingSignalsBatcher|'s [=trusted bidding signals batcher/length limit=] to |ig|'s [=interest group/max trusted bidding signals url length=]. </div> +<h3 id=trusted-seller-signals-batcher>Trusted scoring signals batcher</h3> + +A <dfn>trusted scoring signals batcher</dfn> helps manage merging multiple trusted scoring signals +requests into smaller number of fetches. It's a [=struct=] with the following [=struct/items=]: + +<dl dfn-for="trusted scoring signals batcher"> + : <dfn>request queue</dfn> + :: A [=list=] of [=trusted scoring signals requests=] that hasn't yet been organized to aid + batching. + : <dfn>request map</dfn> + :: A [=map=] from a tuple of [=script fetcher=], a [=URL=] representating the trusted signals + base [=URL=], {{unsigned short}} or null for experiment ID, and + [=origin=], representing the top frame's [=origin], to a [=list=] of [=trusted scoring signals + requests=]. This organizes fetches that can possibly be merged together. + : <dfn>url length limit</dfn> + :: A {{long}} denoting a user-configured limit which should not be exceeded due to combining of + fetches. 0 denotes no limit, and is the initial value. +</dl> + +A <dfn>trusted scoring signals request</dfn> is a [=struct=] with the following [=struct/items=], +representing all information needed to produce a trusted scoring signals fetch for a single scoring +invocation: + +<dl dfn-for="trusted scoring signals request"> + : <dfn>seller</dfn> + :: An [=origin=] of the seller the fetch is on behalf of. + : <dfn>seller script fetcher</dfn>. + :: A [=script fetcher=] for the seller's decision logic script. + : <dfn>base url</dfn> + :: A [=URL=], the base URL for trusted seller signals to fetch. + : <dfn>seller experiment group id</dfn> + :: Null or an {{unsigned short}}, initially null. + : <dfn>top level origin</dfn> + :: An [=origin=]. The origin of top-level frame in the hierarchy containing the document running + the auction. + : <dfn>render URL</dfn> + :: A [=string=], the serialized main URL of the creative to pass in the fetch. + : <dfn>ad component URLs</dfn> + :: A [=set=] of [=strings=], list of serialized component URLs of the creative to pass in the + fetch. + : <dfn>policy container</dfn> + :: A [=policy container=] to use for the network fetch. + : <dfn>reply</dfn> + :: A [=trusted scoring signals reply=], null, or failure. Initially null. Set to a non-null value + when the fetch has been completed. +</dl> + +A <dfn>trusted scoring signals reply</dfn> is a [=struct=] with the following [=struct/items=] +<dl dfn-for="trusted scoring signals reply"> + : <dfn>all trusted scoring signals</dfn> + :: A [=ordered map=] or failure. + : <dfn>data version</dfn> + :: An {{unsigned long}} or null. +</dl> + +<div algorithm> + +To <dfn>create a trusted scoring signals batcher</dfn> given a {{long}} |urlLengthLimit|: + +1. Let |batcher| be a new [=trusted scoring signals batcher=]. +1. Set |batcher|'s [=trusted scoring signals batcher/url length limit=] to |urlLengthLimit|. +1. Let |queue| be the result of [=starting a new parallel queue=]. +1. [=parallel queue/enqueue steps|Enqueue the following steps=] to |queue|: + 1. [=Batch and fetch trusted scoring signals=] given |batcher|. +1. Return |batcher|. + +</div> + +<div algorithm> + +To <dfn>fetch trusted scoring signals with batching</dfn> given a [=trusted scoring signals +batcher=] |batcher| and a [=trusted scoring signals request=] |request|: +1. [=list/Append=] |request| to |batcher|'s [=trusted scoring signals batcher/request queue=]. +1. Wait until |request|'s [=trusted scoring signals request/reply=] is non-null. +1. Return |request|'s [=trusted scoring signals request/reply=]. + +</div> + +<div algorithm> + +To <dfn>build trusted scoring signals url</dfn> given a [=URL=] |signalsUrl|, a [=list=] of +[=strings=] |renderURLs|, an [=ordered set=] of [=strings=] |adComponentRenderURLs|, an +{{unsigned short}} |experimentGroupId|, and an [=origin=] |topLevelOrigin|: + +Note: When trusted scoring signals fetches are not batched, |renderURLs|'s [=list/size=] is 1. + +1. Let |queryParamsList| be a new empty [=list=]. +1. [=list/Append=] "hostname=" to |queryParamsList|. +1. [=list/Append=] the result of [=string/UTF-8 percent-encoding=] |topLevelOrigin| using + [=component percent-encode set=] to |queryParamsList|. +1. If |renderURLs| is not [=set/is empty|empty=]: + 1. [=list/Append=] "`&renderURLs=`" to |queryParamsList|. + 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with + |renderURLs|. +1. If |adComponentRenderURLs| is not [=set/is empty|empty=]: + 1. [=list/Append=] "`&adComponentRenderURLs=`" to |queryParamsList|. + 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with + |adComponentRenderURLs|. +1. If |experimentGroupId| is not null: + 1. [=list/Append=] "`&experimentGroupId=`" to |queryParamsList|. + 1. [=list/Append=] [=serialize an integer|serialized=] |experimentGroupId| to |queryParamsList|. +1. Set |signalsUrl|'s [=url/query=] to the result of [=string/concatenating=] |queryParamsList|. +1. Return |signalsUrl|. + +</div> + +<div algorithm> +To <dfn>build batched trusted scoring signals url</dfn> given a non-empty [=list=] of [=trusted +scoring signals requests=] |entriesToBatch|: + +1. Let |firstEntry| be |entriesToBatch|[0]. +1. Let |renderURLs| be an empty [=ordered set=] of [=strings=]. +1. Let |adComponentRenderURLs| be an empty [=ordered set=] of [=strings=] + |adComponentRenderURLs| +1. [=list/For each=] |entry| of |entriesToBatch|: + 1. [=Assert=] that |entry|'s [=trusted scoring signals request/base URL=] is equal to + |firstEntry|'s [=trusted scoring signals request/base URL=]. + 1. [=Assert=] that |entry|'s [=trusted scoring signals request/seller experiment group id=] is + equal to |firstEntry|'s [=trusted scoring signals request/seller experiment group id=]. + 1. [=Assert=] that |entry|'s [=trusted scoring signals request/top level origin=] is equal to + |firstEntry|'s [=trusted scoring signals request/top level origin=]. + 1. [=Assert=] that |entry|'s [=trusted scoring signals request/seller script fetcher=] is equal to + |firstEntry|'s [=trusted scoring signals request/seller script fetcher=]. + 1. [=Assert=] that |entry|'s [=trusted scoring signals request/policy container=] is equal to + |firstEntry|'s [=trusted scoring signals request/policy container=]. + 1. [=set/Append=] |entry|'s [=trusted scoring signals request/render URL=] to |renderURLs|. + 1. [=list/Extend=] |adComponentRenderURLs| with |entry|'s [=trusted scoring signals request/ + ad component URLs=]. +1. Return the result of [=building trusted scoring signals url=] with |firstEntry|'s [=trusted + scoring signals request/base URL=], |renderURLs|, |adComponentRenderURLs|, |firstEntry|'s + [=trusted scoring signals request/seller experiment group id=], + |firstEntry|'s [=trusted scoring signals request/top level origin=]. + +</div> + +<div algorithm> +To <dfn>check if trusted scoring signals batch honors URL length limit</dfn> given a +[=trusted scoring signals batcher=] |batcher| and a non-empty [=list=] of [=trusted +scoring signals requests=] |entriesToBatch|: + +1. If the [=list/size=] of |entriesToBatch| is 1, return true. +1. If |batcher|'s [=trusted scoring signals batcher/url length limit=] is 0, return true. +1. Return whether the [=string/length=] of the result of [=building batched trusted scoring signals + url=] for |entriesToBatch| &le; |batcher|'s [=trusted scoring signals batcher/url length + limit=]. + +</div> + +<div algorithm> + +To <dfn>batch and fetch trusted scoring signals</dfn> given a [=trusted scoring signals batcher=] +|batcher|: + +1. [=Assert=] that these steps are running [=in parallel=]. +1. Until |batcher| is no longer needed: + 1. Wait until at least one of the following is true: + * |batcher|'s [=trusted scoring signals batcher/request queue=] [=list/is not empty=]. + * |batcher|'s [=trusted scoring signals batcher/request map=] [=map/is not empty=] and some + heuristically chosen amount of time has passed. + 1. Atomically do: + 1. Let |incomingRequests| be a [=list/clone=] of |batcher|'s [=trusted scoring signals batcher/ + request queue=]. + 1. [=list/Empty=] |batcher|'s [=trusted scoring signals batcher/request queue=]. + + Note: the result of atomicity is that any concurrent attempts to modify |batcher|'s [=trusted + scoring signals batcher/request queue=] while these steps are running will not result in + different items being removed by the [=list/Empty=] operation than were cloned into + |incomingRequests|. + + 1. [=list/For each=] |request| in |incomingRequests|: + 1. Let |key| be (|request|'s [=trusted scoring signals request/seller script fetcher=], + |request|'s [=trusted scoring signals request/base url=], |request|'s [=trusted scoring + signals request/seller experiment group id=], |request|'s [=trusted scoring signals request/ + top level origin=]). + 1. If |batcher|'s [=trusted scoring signals batcher/request map=][|key|] does not [=map/exist=], + then [=map/set=] |batcher|'s [=trusted scoring signals batcher/request map=][|key|] to an + [=list/is empty|empty=] [=list=]. + 1. [=list/Append=] |request| to |batcher|'s [=trusted scoring signals batcher/request + map=][|key|]. + 1. Some number of times, heuristically, select a |key| and a non-[=list/is empty|empty=] [=set/ + subset=] of |batcher|'s [=trusted scoring signals batcher/request map=][|key|], called + |entriesToBatch|, such that [=checking if trusted scoring signals batch honors URL length + limit=] on |batcher| and |entriesToBatch| returns true: + + Note: implementations are free to wait to collect more requests to merge (by leaving things + in the |batcher|'s [=trusted scoring signals batcher/request map=]), or send them + individually, but need to take into account the configured URL length limit if they do combine + requests. All entries need to be handled eventually. + + 1. [=list/Remove=] |entriesToBatch| from |batcher|'s [=trusted scoring signals batcher/ + request map=][|key|]. + 1. If |batcher|'s [=trusted scoring signals batcher/request map=][|key|] [=list/is empty=], + then [=map/remove=] |key| from |batcher|'s [=trusted scoring signals batcher/request map=]. + 1. Let |fullSignalsUrl| be the result of [=building batched trusted scoring signals url=] for + |entriesToBatch|. + 1. Let |seller| be |entriesToBatch|[0]'s [=trusted scoring signals request/seller=]. + 1. Let |scriptFetcher| be |entriesToBatch|[0]'s [=trusted scoring signals request/seller + script fetcher=]. + 1. Let |policyContainer| be |entriesToBatch|[0]'s [=trusted scoring signals request/policy + container=]. + 1. If |fullSignalsUrl|'s [=url/origin=] is not [=same origin=] with |seller| then: + 1. Let |allowCrossOriginTrustedScoringSignalsFrom| be the result of [=wait for cross origin + trusted scoring signals authorization from a fetcher=] given |scriptFetcher|. + 1. If |allowCrossOriginTrustedScoringSignalsFrom| does not [=list/contain=] + |fullSignalsUrl|'s [=url/origin=], set |fullSignalsUrl| to null. + 1. Let |result| be failure. + 1. If |fullSignalsUrl| is not null: + 1. Let «|allTrustedScoringSignals|, <var ignore>ignored</var>, |scoringDataVersion|» be + the result of [=fetching trusted signals=] with |fullSignalsUrl|, |seller|, + |policyContainer|, and false. + 1. If |allTrustedScoringSignals| is an [=ordered map=]: + 1. Set |result| to a new [=trusted scoring signals reply=] + 1. Set |result|'s [=trusted scoring signals reply/all trusted scoring signals=] to + |allTrustedScoringSignals|. + 1. Set |result|'s [=trusted scoring signals reply/data version=] to |scoringDataVersion|. + 1. [=list/For each=] |entry| in |entriesToBatch|: + 1. Set |entry|'s [=trusted scoring signals request/reply=] to |result|. + +</div> + <h3 id=generated-bid-header>Generated bid</h3> A <dfn>generated bid</dfn> is a bid that needs to be scored by the seller. The bid is either the @@ -5943,10 +6925,8 @@ To <dfn>try to reach component ads target considering k-anonymity</dfn>, given a 1. Let |selectedComponents| be a new [=list=] of [=ad descriptors=]. 1. [=set/For each=] |i| of [=list/get the indices=] of |generatedBid|: 1. Let |candidateComponent| be |generatedBid|'s [=generated bid/ad component descriptors=][|i|]. - 1. If [=query component ad k-anonymity count=] given |candidateComponent|'s [=interest group ad/render url=] returns true: - - Issue: TODO: change to query k-anonymity cache instead. - (<a href="https://github.com/WICG/turtledove/issues/1150">WICG/turtledove#1150</a>) + 1. Compute |componentAdHashCode| by getting the result of [=compute the key hash of component ad=] given |candidateComponent|'s [=interest group ad/render url=]. + 1. If [=query k-anonymity cache=] given |componentAdHashCode| returns true: 1. [=list/Append=] |candidateComponent| to |selectedComponents|. 1. Otherwise: 1. If |i| &lt; |generatedBid|'s [=generated bid/number of mandatory ad components=], return false. @@ -5998,6 +6978,7 @@ A <dfn>bid debug reporting info</dfn> is a [=struct=] with the following [=struc : <dfn>interest group owner</dfn> :: An [=origin=]. Matches [=interest group/owner=] of the interest group that was used to call `generateBid()` to produce this reporting information. + : <dfn>bidder debug win report url</dfn> :: Null or a [=URL=], initially null. Set by `generateBid()`'s {{InterestGroupBiddingAndScoringScriptRunnerGlobalScope/forDebuggingOnly}}'s @@ -6269,6 +7250,38 @@ An <dfn>auction report info</dfn> 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. + : <dfn>real time reporting contributions map</dfn> + :: A [=real time reporting contributions map=] +</dl> + +A <dfn>real time reporting contributions map</dfn> is an [=ordered map=] whose [=map/keys=] are +[=origins=] and whose [=map/values=] are [=lists=] of [=real time reporting contributions=]. + +A <dfn>real time reporting contribution</dfn> is a [=struct=] with the following [=struct/items=]: + +<dl dfn-for="real time reporting contribution"> + : <dfn>bucket</dfn> + :: A {{long}}. Must be greater than 0 and less than the sum of [=number of user buckets=] and + [=number of platform buckets=]. + : <dfn>priority weight</dfn> + :: A {{double}}. Must be greater than 0 and less than infinity, dictates the relative likelihood + of which bucket will get the contribution. + : <dfn>latency threshold</dfn> + :: Null or a [=duration=] in milliseconds. Initially null. + Reports when a latency (e.g., `generateBid()` execution latency) is greater than this threshold. +</dl> + +<h3 id=k-anonymity-records>K-Anonymity Records</h3> +A <dfn>k-anonymity key</dfn> is a [=string=] used as a key for tracking k-anonymity status. + +A <dfn>k-anonymity record</dfn> is a timestamped cache of the k-anonymity status +for a given [=k-anonymity key=]. These records are stored in the [=user agent=]. + +<dl dfn-for="k-anonymity record"> + : <dfn>is k-anonymous</dfn> + :: A {{boolean}} indicating whether the [=k-anonymity key=] indicated by this record was reported as k-anonymous. + : <dfn>timestamp</dfn> + :: The [=moment=] when the k-anonymity status in this record was last fetched. </dl> # Privacy Considerations # {#privacy-considerations}
Group memberInterest group field