diff --git a/FLEDGE_Key_Value_Server_API.md b/FLEDGE_Key_Value_Server_API.md index 95e8410e5..b7b4a680b 100644 --- a/FLEDGE_Key_Value_Server_API.md +++ b/FLEDGE_Key_Value_Server_API.md @@ -135,6 +135,18 @@ In the request, one major difference from V1/BYOS is that the keys are now group }, "description": "Algorithm accepted by the browser for the response." }, + "metadata": { + "title": "tkv.request.v2.RequestMetadata", + "description": "metadata", + "type": "object", + "additionalProperties": false, + "properties": { + "hostname": { + "description": "The hostname of the top-level frame calling runAdAuction().", + "type": "string" + } + } + }, "partitions": { "description": "A list of partitions. Each must be processed independently. Accessible by UDF.", "type": "array", @@ -158,10 +170,6 @@ In the request, one major difference from V1/BYOS is that the keys are now group "type": "object", "additionalProperties": false, "properties": { - "hostname": { - "description": "The hostname of the top-level frame calling runAdAuction().", - "type": "string" - }, "experimentGroupId": { "type": "string" }, @@ -267,12 +275,14 @@ Example trusted bidding signals request from Chrome: "none", "gzip" ], + "metadata": { + "hostname": "example.com" + }, "partitions": [ { "id": 0, "compressionGroupId": 0, "metadata": { - "hostname": "example.com", "experimentGroupId": "12345", "slotSize": "100,200", }, diff --git a/PA_Feature_Detecting.md b/PA_Feature_Detecting.md index 0afb38a3a..150872ba7 100644 --- a/PA_Feature_Detecting.md +++ b/PA_Feature_Detecting.md @@ -139,6 +139,7 @@ navigator.protectedAudience && navigator.protectedAudience.queryFeatureSupport( ``` ## Selectable Reporting IDs +[Intent to Ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/1cWqBPHngd0) From context of a web page: ``` diff --git a/meetings/2024-11-30-FLEDGE-call-minutes.md b/meetings/2024-10-30-FLEDGE-call-minutes.md similarity index 100% rename from meetings/2024-11-30-FLEDGE-call-minutes.md rename to meetings/2024-10-30-FLEDGE-call-minutes.md diff --git a/meetings/2024-11-06-FLEDGE-call-minutes.md b/meetings/2024-11-06-FLEDGE-call-minutes.md new file mode 100644 index 000000000..d9ec54866 --- /dev/null +++ b/meetings/2024-11-06-FLEDGE-call-minutes.md @@ -0,0 +1,257 @@ +# 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 Nov 6, 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 (Individual CLA commitment) +3. Shivani Sharma (Google Privacy Sandbox) +4. Roni Gordon (Index Exchange) +5. Pooja Muhuri (Google Privacy Sandbox) +6. Sven May (Google Privacy Sandbox) +7. Paul Jensen (Google Privacy Sandbox) +8. David Dabbs (Epsilon) +9. Matt Davies (Bidswitch | Criteo) +10. Garrett McGrath (Magnite) +11. Harshad Mane (PubMatic) +12. Luckey Harpley (Remerge.io) +13. Matt Kendall (Index Exchange) +14. David Eilertsen (Remerge) +15. Orr Bernstein (Google Privacy Sandbox) +16. Laurentiu Badea (OpenX) +17. Tim Hsieh (Google Ad Manager) +18. Alonso Velasquez (Google Privacy Sandbox) +19. Sathish Manickam (Google Privacy Sandbox) +20. Abed Islam (Google Privacy Sandbox) +21. Peiwen Hu (Google Privacy Sandbox) +22. Donato Borrello (Google Ad Manager - Video) +23. Jeremy Bao (Google Privacy Sandbox) +24. Diana Torres (MLB) +25. Abishai Gray (Google Privacy Sandbox) +26. Shafir Uddin (Raptive) +27. Koji Ota (CyberAgent) +28. Isaac Foster (MSFT Ads) +29. Yanush Piskevich(Microsoft) +30. Arthur Coleman (ThinkMedium) +31. Andrew Pascoe (NextRoll) + + +## Note taker: Orr Bernstein + + +# Agenda + +_Note: If you tried to join the meeting but were denied, please try again. Sorry about that_ + + +## 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) + + + +* [Alonso, Google Privacy Sandbox] Proposals for changes on forDebuggingOnly and Real-Time Monitoring APIs based on user choice state for 3p cookies: + * https://github.com/WICG/turtledove/issues/632#issuecomment-2455842300 + * https://github.com/WICG/turtledove/issues/430#issuecomment-2455844071 +* [Shivani Sharma, Google Privacy Sandbox] Gather initial feedback from ecosystem partners on instream video support with FFs: https://github.com/WICG/turtledove/issues/1318 +* [Peiwen Hu, Google Privacy Sandbox] Quick announcement: will talk about passing contextual signals to trusted key value server in the protected auction server WICG call happening right after this meeting. https://github.com/WICG /protected-auction-services-discussion/issues/27 +* + + +# Announcements + +Privacy Sandbox folks are holding every-second-Wednesday meetings in the hour after this meeting to discuss the trusted server elements of Protected Audience work. For more information see https://github.com/WICG/protected-auction-services-discussion/issues/27. + +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 + + +## [Peiwen Hu, Google Privacy Sandbox] Quick announcement: will talk about passing contextual signals to trusted key value server in the protected auction server WICG call happening right after this meeting. + + + +* Peiwen Hu + * Another WICG meeting for servers - includes B&A server and Key/Value Server for PA. + * Will talk about passing contextual signals from PA flow to Trusted K/V servers + * Would like to get feedback on people's interest and what people think about. + * If people would like to discuss in this forum, happy to discuss in future session of this forum as well. +* Isaac Foster + * To clarify, this is the plan for the conversation in the meeting one hour from now? +* Peiwen + * Yes. +* Michael Kleber + * A few other meetings + * Meeting on the server-side of PA development, every other week including today, immediately following this one. + * Another call, every other week from the Microsoft EDGE folks, on Thursdays at 11:00 am eastern time (24 hours after this meeting), but not this week - the opposite Thursday. +* (Link to that meeting is here: https://github.com/WICG/protected-auction-services-discussion/issues/27) +* Isaac Foster + * Not available at this week's servers meeting, but in strong support. + + +## [Alonso, Google Privacy Sandbox] Proposals for changes on forDebuggingOnly and Real-Time Monitoring APIs based on user choice state for 3p cookies: + + + +* https://github.com/WICG/turtledove/issues/632#issuecomment-2455842300 +* https://github.com/WICG/turtledove/issues/430#issuecomment-2455844071 +* Alonso + * (Presenting https://github.com/WICG/turtledove/issues/632#issuecomment-2455842300) + * When Chrome detects that 3PCs are allowed in the current site, FDO and real-time monitoring - it's possible for these APIs to be unsampled or unnoised. + * The downsampling algorithm will stay in place, the cooldown algorithm will stay in place. +* David Dabbs + * Is this a sort of, here's what we intend to do if we don't hear otherwise? +* Alonso + * Like everything else, it's a proposal. +* Roni Gordon + * I don't understand the third paragraph of this update. + * What does it actually mean in practice when you allow 3PC in the “current impression site”? +* Michael Kleber + * The easiest way to describe this is in terms of how Chrome works right now - no need to make guesses about how the UI will look in the future. + * Right now, in Chrome, it's possible for users to have 3PCs turned on or turned off. Even if a user has 3PCs turned off, there's a little eye icon in the top-right corner of the URL bar in which users can enable third party cookies for the current site, where enabling 3PCs might fix the current site. That expression takes into consideration the full context of whether 3PCs are enabled for that site based on that combination of settings. + * Note, Alonso - you were pointing to two different things - FDO, sampling; and also real-time monitoring. +* Alonso + * (Presenting https://github.com/WICG/turtledove/issues/430#issuecomment-2455844071) + * Real-time monitoring - when 3PC are allowed, the noising algorithm will be turned so that noise is set to zero. + * Ad tech, you'll be able to see a particular contribution. +* Roni Gordon + * Equivalent signal planned for FDO, so we know if we need to be sampling or not? +* Alonso + * With sampling, the algorithm will handle sampling. Do you mean that you would adjust sampling on your own? +* Roni + * Not currently in cooldown/lockout state, but it's just about understanding what the nature of this is going to be so you could understand the data set. But with UCC, some user agents will be sending it all the time, and others will be subject to lockout/cooldown. Trying to make sense of this duality. +* Alonso + * Today, the API has a flag that tells you whether this particular contribution would have been sampled, and this exists because we launched it in June so that ad tech could use that to understand, with the current settings for lockdown/cooldown, whether this works for analysis. Paul, if this same signal continues to be available, would this answer Roni's concern? +* Paul Jensen + * Not sure, because it would indicate that a user is in lockdown/cooldown. + * Checking the 3PC presence might be a way to indicate, or could maybe expose it as a feature detection, but that might be redundant. +* Alonso + * Roni, you're just looking at impression side, right? +* Roni + * No, not necessarily. Would have to check, but do we get our 3PCs attached to these sendReportTo callbacks? +* Paul + * Before the seller constructs an auction config, could check for the presence of 3PCs and indicate to determine if FDO is going to be downsampled or not. +* Roni + * That's my question - will FDO provide this, or do we need to pass it back to myself? +* Michael Kleber + * Not sure how to do that. In aggregatable reports case, it's easy for us to add another URL parameter. But for fDO, the URL is in the hands of the ad tech; could pass the signal to the JS environment and let the ad tech use that. + * I think the question is, when a caller gets a bunch of reports, some are downsampled and some are not, so they can't aggregate that without knowing which are which. +* Paul + * But we can expose that in the API, right next to the worklet. +* Roni + * Yes, can I get a signal that says, alongside whether the device is lockdown/cooldown, to indicate that this is unsampled. +* Michael + * Yes, this seems reasonable and something we should take as a feature request. +* David Dabbs + * In your fourth paragraph, mentioned cookie choice state change as the lever - that's the, I want to turn them off globally? Just want to understand the "globally" vs "site". +* Alonso + * There are possible scenarios that the API will detect from one state to the other. +* David Dabbs + * I think you answered it; if I joined some IGs yesterday, and then today I turned off 3PCs, then it's smart enough to know that these IGs will expire in 90 days. +* Michael Kleber + * For these kinds of questions, don't have an answer nailed down, should wait until we get more of the engineering. +* Brian May + * What if users turn the cookies on and then back off; do we know how this would work? +* Michael + * Again, for such corner cases, we should wait for the engineering to happen and we can read back what happens. That paragraph sounds like users changing their global browser controls, and what happens when users change their settings for one site, it's not obvious what the right answer should be. +* Arthur asked by chat + * what is the timeline for the engineering? +* Alonso + * TBD +* Roni + * Independently of when you get it on your TODO list, is this tied to user choice, tied to that decision, or implemented independently? +* Alonso + * Evaluation and changes in algorithm - all of the logic that you see today would run. If the PA API team is able to implement this earlier, we'll share that. + + +## [Shivani Sharma, Google Privacy Sandbox] Gather initial feedback from ecosystem partners on instream video support with FFs: https://github.com/WICG/turtledove/issues mi/1318 + + + +* Shivani Sharma + * As mentioned in the issue log, seeking ecosystem feedback - assumptions made while creating this design. First of the hopefully more discussions that we'll have on in-stream video support within Fenced Frames. + * Four aspects of the design that it goes into. + * 1. Based on the assumption that there's a requirement to have a UI coordinated between content and the ad that goes in; transitions between ad/content and back, similar look and feel of controls, and also when users interact with the controls, e.g. adjust volume, should apply to both ad/content. The first two parts of the design - Media and Control - are related. + * 2. How does VAST actually go to the player? Last part is about VAST tracking events and reporting in general. + * 3. What we developed in PA for non-video ads or banner ads and applying it to video in-stream. In this case though it's not just the renderURL, but rather a combination of the video player and the VAST XML. + * 4. Reporting in the current state, what this issue is suggesting for instream video, currently do it via FFAR; already has support for component sellers, top-level sellers and buyers as different entities. Would like to have FFAR being used for post-render reporting. + * Any questions? +* Roni Gordon + * Curious about the third part. Looking at the non-fenced frame proposal. Generally speaking, the buyer has no role in providing the video player whatsoever. Donato mentioned it on the GitHub issue as well. Not sure that actually works. +* Shivani + * Would like to hear from buyers as well. Let's say there was some coordination between buyer and video players that are integrated with PA and FFAR. Let's say there were two or three different video players, and they could add them to their renderURL. +* Roni + * I'm representing seller. Could have different video player for different sellers, so can't necessarily have baked into renderURL. +* Shivani + * How does this impact on k-anonymity? Would have impact on the privacy model. +* Roni + * Why the original macro +* Donato Borello + * Some information, e.g. volume. But otherwise, video player is a black box - why publishers don't have to care about what video player a seller is using. All video players can conform to the API. +* Shivani + * Video player and VAST, and jointly k-anonymous. If we're talking about seller providing video player, would have to be very careful about privacy. If it's one seller, multiple video players, would have to be careful. Definitely can't be user-specific, would need to be global in some way. +* Donato + * What if it's selected in ads.txt, so that it can't be personalized by user and can't be hard-coded by the publisher? +* Shivani + * Will take this input and see how it can fit in the design in a privacy-safe manner + * First and second are about how the media player is synchronized, bit rate/volume - communicated on the start/end events. Underspecified what these rate limits look like; intentionally underspecified as we're still looking at how the privacy characteristics look. Limited number of bits to communicate, e.g. volume. Still TBD. + * If the user interacted with the fenced frame at the beginning or have autoplay granted, that's when the data coming into the fenced frame are available to the fenced frame. +* Patrick McCann + * Criteria for autoplay being granted on? +* Shivani + * Autoplay criteria is something the browser already has. Combination of user activation media engagement index. +* Patrick + * So, not changing, whatever they are now? +* Shivani + * Yes, that's correct. If the fenced frame doesn't get autoplay, then the fenced frame doesn't even get that data unless/until user activation. + * Second part is about the controls. In today's world, same user controls used for the content being played and ad being played. In this proposal where fenced frame is a separate boundary, there are inputs about the controls needed from the context, and from the ad itself. Combination of these two - which is why we need a separate privacy boundary - which is why it's in a Fenced Frame. +* Matt Kendall + * Is the intention that VAST redirects are not supported? +* Shivani + * If it's the VAST XML that needs to be wrapped by different entities.. in this proposal, we're not suggesting that seller and component seller be able to wrap the VAST XML. Today, that's used for reporting. Instead, they could register a beacon in their worklets, and reporting would happen for all of the registered events and entities. +* Matt + * Thinking specifically about the ad itself, would need to be a media file, can't be another VAST document, is that correct? +* Shivani + * Yes. VAST XML is provided by the buyer. +* Matt + * My understanding of how this works today is that there's a daisy chain of VAST XMLs, fairly common how this works today, might be a challenge for this proposal. +* Shivani + * If root XML is the buyer's, and it has all of the daisy chain inside it. +* Matt + * So, same origin restriction? +* Shivani + * No, but for the root XML, if it's provided +* Donato + * Chaining in theory works, but not in practice, because you can't inject the renderURL. + * Because publisher or seller can provide the player they want, don't have to worry about the daisy chaining. Most likely seller or component seller will need to set the set of events they need to trigger. Otherwise, FFAR would be difficult to coordinate between groups. + * If component seller is using registered beacons, need to have engagement about what beacons would be set. +* Shivani + * IAB standard could be used to coordinate what these beacons should be. + * Any other questions? Please add to the GH issue. +* Alonso + * Sooner we get feedback on how feasible this is, the more we have internally a chance to figure out what changes we need to do. Please give the GitHub issue a read. diff --git a/spec.bs b/spec.bs index 8b80987dc..7fd86841e 100644 --- a/spec.bs +++ b/spec.bs @@ -291,11 +291,11 @@ This is detectable because it can change the set of fields that are read from th 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 + 1. Set |interestGroup|'s [=interest group/expiry=] to the [=current coarsened wall time=] plus |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=]. + 1. Set |interestGroup|'s [=interest group/next update after=] to the [=current coarsened wall + time=] plus 24 hours. + 1. Set |interestGroup|'s [=interest group/last updated=] to the [=current coarsened wall time=]. 1. Set |interestGroup|'s [=interest group/owner=] to the result of [=parsing an https origin=] on |group|["{{GenerateBidInterestGroup/owner}}"]. 1. If |interestGroup|'s [=interest group/owner=] is failure, then [=exception/throw=] a {{TypeError}}. @@ -489,7 +489,7 @@ This is detectable because it can change the set of fields that are read from th the currently stored one from the browser. 1. Set |interestGroup|'s [=interest group/joining origin=] to [=this=]'s [=relevant settings object=]'s [=environment/top-level origin=]. - 1. Set |interestGroup|'s [=interest group/join time=] to the [=current wall time=]. + 1. Set |interestGroup|'s [=interest group/join time=] to the [=current coarsened wall time=]. 1. If the most recent entry in |interestGroup|'s [=interest group/join counts=] corresponds to 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. @@ -532,7 +532,7 @@ To perform storage maintenance: It's sorted based on their [=map/values=] ([=interest group/expiry=]) in descending order, in order to remove [=interest groups=] of [=interest group/owners=] expiring soonest first. -1. Let |now| be the [=current wall time=]. +1. Let |now| be the [=current coarsened wall time=]. 1. [=list/For each=] |ig| of the [=user agent=]'s [=interest group set=]: 1. Let |owner| be |ig|'s [=interest group/owner=]. 1. If |ig|'s [=interest group/expiry=] is before |now|, then [=list/remove=] |ig| from the @@ -1646,7 +1646,7 @@ To validate and convert auction ad config given an {{AuctionAdConfig} whose [=interest group/owner=] is |ig|'s [=interest group/owner=] and whose [=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. Set |win|'s [=previous win/time=] to the [=current coarsened wall time=]. 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=] @@ -1752,13 +1752,14 @@ a {{DirectFromSellerSignalsForBuyer}} |directFromSellerSignalsForBuyer|, a [=dur [=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=]). +[=list=] of [=real time reporting contributions=], [=Private Aggregation contributions=], +[=execution metrics=]). 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=] - minus |ig|'s [=interest group/join time=], in milliseconds. + 1. Set |browserSignals|["{{BiddingBrowserSignals/recency}}"] to the [=current coarsened wall + time=] minus |ig|'s [=interest group/join time=], in milliseconds. 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/bidCount}}"] to the sum of |ig|'s [=interest group/bid counts=] for all days within the last 30 days. 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/adComponentsLimit}}"] to 40. @@ -1784,17 +1785,17 @@ following steps. They return a failure if failing to fetch the script or wasm, o |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. [=Add a sample to an averager=] given |metrics|'s [=execution metrics/average code fetch - time=] and |biddingScriptFetcher|'s [=script fetcher/fetch duration=]. + 1. [=Add a sample to an averager=] given |metrics|'s [=execution metrics/code fetch time + averager=] and |biddingScriptFetcher|'s [=script fetcher/fetch duration=]. 1. If |biddingScript| is failure, return failure. 1. If |ig|'s [=interest group/bidding wasm helper url=] is not null: 1. Let |wasmFetchStart| be |settings|'s [=environment settings object/current monotonic time=]. 1. Let |wasmModuleObject| be the result of [=fetching WebAssembly=] with |ig|'s [=interest group/bidding wasm helper url=] and |settings|. 1. Let |wasmFetchDuration| be the [=duration from=] |wasmFetchStart| to - |settings|'s [=environment settings object/current monotonic time=], in milliseconds. - 1. [=Add a sample to an averager=] given |metrics|'s [=execution metrics/average code fetch - time=] and |wasmFetchDuration|. + |settings|'s [=environment settings object/current monotonic time=], in milliseconds. + 1. [=Add a sample to an averager=] given |metrics|'s [=execution metrics/code fetch time + averager=] and |wasmFetchDuration|. 1. If |wasmModuleObject| is not failure, then [=map/set=] |browserSignals|["{{BiddingBrowserSignals/wasmHelper}}"] to |wasmModuleObject|. 1. Otherwise, return failure. @@ -1833,7 +1834,7 @@ and a [=real time reporting contributions map=] |realTimeContributionsMap|: 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 |auctionStartTime| be the [=current coarsened wall time=]. 1. Let |decisionLogicFetcher| be the result of [=creating a new script fetcher=] with |auctionConfig|'s [=auction config/decision logic url=] and |settings|. 1. Let |trustedScoringSignalsBatcher| be the result of [=creating a trusted scoring signals @@ -1991,9 +1992,8 @@ and a [=real time reporting contributions map=] |realTimeContributionsMap|: 1. [=map/For each=] slotSizeQueryParam → |perSignalsUrlGenerator| of |perSlotSizeQueryParam|: 1. [=map/For each=] joiningOrigin → |groups| of |perSignalsUrlGenerator|: 1. [=list/Remove=] from |groups| any [=interest group=] [=list/contained=] in |igs|. - 1. Set |metrics|'s [=per participant metrics/participating interest group count=] to - |metrics|'s [=per participant metrics/participating interest group count=] + [=list/ - size=] of |groups|. + 1. Increment |metrics|'s [=per participant metrics/participating interest group count=] + by [=list/size=] of |groups|. 1. Let |perBuyerSignals| be null. 1. If |auctionConfig|'s [=auction config/per buyer signals=] is not null and [=auction config/per buyer signals=][|buyer|] [=map/exists=], then set |perBuyerSignals| to @@ -2379,19 +2379,17 @@ or "component-auction", a [=currency tag=] |componentAuctionExpectedCurrency|, a 1. Return. 1. Let |metrics| be the result of [=access per-participant metrics=] given |reportingContext|, |auctionConfig|'s [=auction config/seller=], [=worklet function/score-ad=]. -1. [=Add a sample to an averager=] given |metrics|'s [=per participant metrics/average code fetch - time=] and |decisionLogicFetcher|'s [=script fetcher/fetch duration=]. +1. [=Add a sample to an averager=] given |metrics|'s [=per participant metrics/code fetch time + averager=] and |decisionLogicFetcher|'s [=script fetcher/fetch duration=]. 1. Let « |scoreAdResult|, |debugWinReportUrl|, |debugLossReportUrl|, |realTimeContributions|, |paContributions|, |executionMetrics| » be the result of [=evaluating a scoring script=] with |decisionLogicScript|, |adMetadata|, |bidValue|'s [=bid with currency/value=], |auctionConfig|, |reportingContext|, |sameOriginTrustedScoringSignals|, |crossOriginTrustedScoringSignals|, |browserSignals|, |directFromSellerSignalsForSeller|, and |auctionConfig|'s [=auction config/ seller timeout=]. -1. Set |metrics|'s [=per participant metrics/script executions attempted=] to |metrics|'s [=per - participant metrics/script executions attempted=] + 1. -1. If |executionMetrics|'s [=execution metrics/script timeout occurred=] is true, set |metrics|'s - [=per participant metrics/script timeouts occurred=] to |metrics|'s [=per participant metrics/ - script timeouts occurred=] + 1. +1. Increment|metrics|'s [=per participant metrics/script executions attempted=] by 1. +1. If |executionMetrics|'s [=execution metrics/script timeout occurred=] is true, increment + |metrics|'s [=per participant metrics/script timeouts occurred=] by 1. 1. If |generatedBid|'s [=generated bid/for k-anon auction=] is true: Note: Non-k-anonymous bids do not participate in reporting (except for platform real-time @@ -2932,8 +2930,8 @@ and a [=global object=] |global|: |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. [=Add a sample to an averager=] given |metrics|'s [=per participant metrics/average code fetch - time=] and |sellerReportingScriptFetcher|'s [=script fetcher/fetch duration=]. + 1. [=Add a sample to an averager=] given |metrics|'s [=per participant metrics/code fetch time + averager=] and |sellerReportingScriptFetcher|'s [=script fetcher/fetch duration=]. 1. Let « |sellerSignals|, |reportUrl|, |reportingBeaconMap|, ignored, |paContributions|, |executionMetrics| » be the result of [=evaluating a reporting script=] with |sellerReportingScript|, "`reportResult`", |reportingContext|, @@ -3036,8 +3034,8 @@ a {{ReportingBrowserSignals}} |browserSignals|, a [=direct from seller signals=] |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. [=Add a sample to an averager=] given |metrics|'s [=per participant metrics/average code fetch - time=] and |buyerReportingScriptFetcher|'s [=script fetcher/fetch duration=]. + 1. [=Add a sample to an averager=] given |metrics|'s [=per participant metrics/code fetch time + averager=] and |buyerReportingScriptFetcher|'s [=script fetcher/fetch duration=]. 1. Let |reportFunctionName| be "`reportWin`". 1. If |winner|'s [=generated bid/provided as additional bid=] is true: 1. Set |reportFunctionName| be "`reportAdditionalBidWin`". @@ -3219,9 +3217,9 @@ a [=list=] of [=interest groups=] |bidIgs|, and a [=reporting context map=] [=interest group/owner=] is |igId|'s [=interest group/owner=] and [=interest group/name=] is |igId|'s [=interest group/name=]. [=iteration/Continue=] if none found. 1. If |updateIfOlderThan| is less than 10 mintues, set it to 10 minutes. - 1. If [=current wall time=] − |ig|'s [=interest group/last updated=] ≥ + 1. If [=current coarsened wall time=] − |ig|'s [=interest group/last updated=] ≥ |updateIfOlderThan|, set |ig|'s [=interest group/next update after=] to the - [=current wall time=] + |updateIfOlderThan|. + [=current coarsened wall time=] + |updateIfOlderThan|. 1. Return |winningBidInfo|. @@ -3371,7 +3369,7 @@ A server auction browser signals is a [=struct=] with the following [ :: 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 + :: A [=duration=], in milliseconds, representing the [=current coarsened wall time=] at the time this object was constructed minus the corresponding [=interest group=]'s [=interest group/join time=], in milliseconds. : previous wins @@ -3381,7 +3379,7 @@ A server auction browser signals is a [=struct=] with the following [ 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 + :: A [=duration=], in milliseconds, representing the [=current coarsened 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. @@ -3492,7 +3490,7 @@ The getInterestGroupAdAuctionData(|configIDL|) m 1. Set |config|'s [=auction data config/encryption key=] to |key|. 1. Set |config|'s [=auction data config/encryption key id=] to |keyId|. 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. Let |startTime| be a [=moment=] equal to the [=current coarsened 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=]. @@ -3522,7 +3520,8 @@ The getInterestGroupAdAuctionData(|configIDL|) m : [=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 + :: the [=current coarsened 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=]: @@ -3696,8 +3695,7 @@ To access per-participant metrics given a [=reporting context=] |repo an [=origin=] |origin| and [=worklet function=] |workletFunction|: 1. Let |key| be (|origin|, |workletFunction|). 1. If |reportingContext|'s [=reporting context/participant metrics=][|key|] does not [=map/exist=], - set |reportingContext|'s [=reporting context/participant metrics=][|key|] to a new [=per participant - metrics=]. + set it to a new [=per participant metrics=]. 1. Return |reportingContext|'s [=reporting context/participant metrics=][|key|]. @@ -3741,13 +3739,12 @@ auction in a multi-party auction). |bidDebugReportInfo|. 1. Let |metrics| be the result of [=access per-participant metrics=] given |reportingContext|, |ig|'s [=interest group/owner=], [=worklet function/generate-bid=]. - 1. [=Merge samples to an averager=] given |metrics|'s [=per participant metrics/average code fetch - time=] and |executionMetrics|'s [=execution metrics/average code fetch time=]. + 1. [=Merge samples to an averager=] given |metrics|'s [=per participant metrics/code fetch time + averager=] and |executionMetrics|'s [=execution metrics/code fetch time averager=]. 1. Set |metrics|'s [=per participant metrics/script executions attempted=] to |metrics|'s [=per participant metrics/participating interest group count=]. - 1. If |executionMetrics|'s [=execution metrics/script timeout occurred=] is true, set |metrics|'s - [=per participant metrics/script timeouts occurred=] to |metrics|'s [=per participant metrics/ - script timeouts occurred=] + 1. + 1. If |executionMetrics|'s [=execution metrics/script timeout occurred=] is true, increment + |metrics|'s [=per participant metrics/script timeouts occurred=] by 1. 1. [=set/Insert=] |id| into |reportingContext|'s [=reporting context/bidder participants=]. 1. [=Commit private aggregation contributions=] given |paContributions|, |id| and |reportingContext|. @@ -3868,11 +3865,11 @@ and [=map/values=] are [=moments=] at which the cool down for the origin key exp
To is debugging only in cooldown or lockout given an [=origin=] |origin|: - 1. If [=user agent=]'s [=debug report lockout until=] is not null, and [=current wall time=] is - less than [=user agent=]'s [=debug report lockout until=], return true. + 1. If [=user agent=]'s [=debug report lockout until=] is not null, and [=current coarsened wall + time=] is less than [=user agent=]'s [=debug report lockout until=], return true. 1. If [=user agent=]'s [=debug report cooldown=] is null, then return false. - 1. If [=user agent=]'s [=debug report cooldown=][|origin|] [=map/exists=] and [=current wall time=] - is less than [=user agent=]'s [=debug report cooldown=][|origin|], then return true. + 1. If [=user agent=]'s [=debug report cooldown=][|origin|] [=map/exists=] and [=current coarsened + wall time=] is less than [=user agent=]'s [=debug report cooldown=][|origin|], then return true. 1. Return false.
@@ -3884,15 +3881,15 @@ and [=map/values=] are [=moments=] at which the cool down for the origin key exp chosen with a probability equal to [=sampling rate=]. 1. If |sampleRand| is 0: 1. Set |canSendAfterSampled| to true. - 1. Set [=user agent=]'s [=debug report lockout until=] to [=current wall time=] plus + 1. Set [=user agent=]'s [=debug report lockout until=] to [=current coarsened wall time=] plus [=lockout period=]. 1. Let |cooldownRand| be a random {{long}} ≥ 0 and < 10, which corresponds to [=long cooldown rate=]. 1. Let |cooldownPeriod| be [=long cooldown period=] if |cooldownRand| is 0, [=short cooldown period=] otherwise. - 1. Set [=user agent=]'s [=debug report cooldown=][|origin|] to [=current wall time=] plus - |cooldownPeriod|. + 1. Set [=user agent=]'s [=debug report cooldown=][|origin|] to [=current coarsened wall time=] + plus |cooldownPeriod|. 1. Return |canSendAfterSampled|. @@ -4150,7 +4147,9 @@ A signal base value is one of the following: for this particular worklet function, for this participant. : "participating-ig-count" :: The numeric value is the number of interest groups for the buyer actually participating in the - auction, after considering prioritization and capabilities. + auction, after considering prioritization and capabilities. Interest groups included in this might + not actually get to bid if the cumulative timeout expires, or the script fails to load, etc; or + might decide not to bid, but they would have gotten a chance if nothing went wrong. : "percent-scripts-timeout" :: The numeric value is percentage of executions of this script that hit their individual timeout, out of all executions that were expected to happen. @@ -4213,7 +4212,7 @@ A worklet function is one of the following:
To find corresponding bid and score phase function given a [=worklet function=] |fn|: - 1.Switch on on |fn|: + 1.Switch on |fn|:
: [=worklet function/generate-bid=] :: Return [=worklet function/generate-bid=]. @@ -4238,39 +4237,41 @@ An averager is a a [=struct=] with the following [=struct/items=]:
To add a sample to an averager given an [=averager=] |averager|, a {{double}} |sample|: -1. Set |averager|'s [=averager/count=] to |averager|'s [=averager/count=] + 1. -1. Set |averager|'s [=averager/sum=] to |averager|'s [=averager/sum=] + |sample|. +1. Increment |averager|'s [=averager/count=] by 1. +1. Increment |averager|'s [=averager/sum=] by |sample|.
To merge samples to an averager given [=averagers=] |dest| and |source|: -1. Set |dest|'s [=averager/count=] to |dest|'s [=averager/count=] + |source|'s [averager/count=]. -1. Set |dest|'s [=averager/sum=] to |dest|'s [=averager/sum=] + |source|'s [averager/sum=]. +1. Set |dest|'s [=averager/count=] to |dest|'s [=averager/count=] + |source|'s [=averager/count=]. +1. Set |dest|'s [=averager/sum=] to |dest|'s [=averager/sum=] + |source|'s [=averager/sum=].
To get the value to report from an averager given an [=averager=] |averager|: -1. If |averager|'s [=averager/count=] = 0, return 0. +1. If |averager|'s [=averager/count=] is 0, return 0. 1. Return |averager|'s [=averager/sum=] / [=averager/count=].
### Metrics structures ### {#private-aggregation-metrics-structures} -An execution metrics is a [=struct=] with the following [=struct/items=]: +An execution metrics is a [=struct=] with the following [=struct/items=], representing +metrics collected from a single execution of a worklet function:
- : average code fetch time + : code fetch time averager :: An [=averager=]. : script timeout occurred :: A [=boolean=], initially false.
-A per participant metrics is a [=struct=] with the following [=struct/items=]: +A per participant metrics is a [=struct=] with the following [=struct/items=], +representing metrics aggregated over a particular participant (e.g. bidder or seller). It has:
: participating interest group count :: A {{long}}, initially 0. - : average code fetch time + : code fetch time averager :: An [=averager=]. : script timeouts occurred :: A {{long}}, initially 0. @@ -4644,8 +4645,8 @@ They return a {{double}}.
1. If |signalBaseValue| is "[=signal base value/average-code-fetch-time=]": - 1. Return the result of [=getting the value to report from an averager=] given |metrics|'s [=per - participant metrics/average code fetch time=]. + 1. Return the result of [=getting the value to report from an averager=] given |metrics|'s [=per + participant metrics/code fetch time averager=]. 1. If |signalBaseValue| is "[=signal base value/participating-ig-count=]": 1. Return |bidAndScoreMetrics|'s [=per participant metrics/participating interest group count=]. 1. If |signalBaseValue| is "[=signal base value/percent-scripts-timeout=]": @@ -4736,7 +4737,7 @@ dictionary StorageInterestGroup : AuctionAdInterestGroup { To get storage interest groups for owner given an [=origin=] |owner|: 1. Let |resultIgs| be an empty [=list=]. - 1. Let |now| be a [=moment=] equal to the [=current wall time=]. + 1. Let |now| be a [=moment=] equal to the [=current coarsened wall time=]. 1. [=list/For each=] |ig| of [=user agent=]'s [=interest group set=]: 1. If |ig|'s [=interest group/owner=] does not equal |owner|, then [=iteration/continue=]. 1. Let |resultIg| be an empty {{StorageInterestGroup}} dictionary. @@ -4820,14 +4821,16 @@ dictionary StorageInterestGroup : AuctionAdInterestGroup { *This first introductory paragraph is non-normative.* -{{Window/navigator}}.{{Navigator/createAuctionNonce()}} creates an auction nonce, a -one-time [=version 4 UUID=] uniquely associated with a single call to -{{Window/navigator}}.{{Navigator/runAdAuction()}}. For multi-seller auctions, this ID is uniquely -associated with all {{AuctionAdConfig/componentAuctions}}. This nonce will need to be passed back in -via a subsequent call to {{Window/navigator}}.{{Navigator/runAdAuction()}} via the -{{AuctionAdConfig}}. This is currently only needed for [=auctions=] that use [=additional bids=], -for which the [=auction nonce=] will be included in each [=additional bid=] as a way of ensuring -that those bids are only used in the [=auctions=] for which they were intended. +{{Window/navigator}}.{{Navigator/createAuctionNonce()}} creates an auction nonce, which +is a one-time canonical [=string=] representation of a [=version 4 UUID=] that is uniquely +associated with a single call to {{Window/navigator}}.{{Navigator/runAdAuction()}}. For multi-seller +auctions, a distinct auction nonce can be uniquely associated with each of the +{{AuctionAdConfig/componentAuctions}}. The auction nonce(s) will need to be passed back in via a +subsequent call to {{Window/navigator}}.{{Navigator/runAdAuction()}} via the {{AuctionAdConfig}}. +This is currently only needed for [=auctions=] that use [=additional bids=], in which the auction +nonce is combined with a [=signed additional bid with metadata/seller nonce=] to construct a bid +nonce that must be included in each [=additional bid=]. For backwards compatibility, [=additional +bids=] may include an auction nonce directly in place of a bid nonce. [SecureContext] @@ -4909,15 +4912,19 @@ a [=reporting context map=] |reportingContextMap|, and a [=global object=] |glob 1. Let |auctionNonce| be the [=string representation=] of |auctionConfig|'s [=auction config/auction nonce=]. 1. Let |capturedAdditionalBidsHeaders| be |global|'s [=associated Document's=] - [=node navigable's=] [=traversable navigable's=] [=captured additional bids headers=]. + [=node navigable's=] [=traversable navigable's=] [=traversable navigable/captured ad auction + additional bids headers=]. 1. Let |additionalBids| be a new [=list=] of [=decoded additional bids=]. - 1. [=list/For each=] |encodedSignedAdditionalBid| of |capturedAdditionalBidsHeaders|[|auctionNonce|]: + 1. [=list/For each=] |encodedSignedAdditionalBidWithMetadata| of + |capturedAdditionalBidsHeaders|[|auctionNonce|]: 1. Let |signedAdditionalBid| be the result of running [=forgiving-base64 decode=] with - |encodedSignedAdditionalBid|. + |encodedSignedAdditionalBidWithMetadata|'s [=signed additional bid with metadata/signed + additional bid=]. 1. If |signedAdditionalBid| is failure, then [=iteration/continue=]. 1. Let |additionalBid| be the result of running [=parse a signed additional bid=] given - |signedAdditionalBid|, |reportingContextMap|, |auctionConfig|, |topLevelAuctionConfig|, and - |negativeTargetInfo|. + |signedAdditionalBid|, |reportingContextMap|, |encodedSignedAdditionalBidWithMetadata|'s + [=signed additional bid with metadata/seller nonce=], |auctionConfig|, + |topLevelAuctionConfig|, and |negativeTargetInfo|. 1. If |additionalBid| is not null: 1. [=list/Append=] |additionalBid| to |additionalBids|. 1. Let |bidCopy| be a clone of |additionalBid|. @@ -4927,9 +4934,9 @@ a [=reporting context map=] |reportingContextMap|, and a [=global object=] |glob <div algorithm> To <dfn>parse a signed additional bid</dfn> given a [=byte sequence=] |signedAdditionalBid|, -a [=reporting context map=] |reportingContextMap|, an [=auction config=] |auctionConfig|, -an [=auction config=]-or-null |topLevelAuctionConfig|, and a [=negative target info=] -|negativeTargetInfo|: +a [=reporting context map=] |reportingContextMap|, a [=string=]-or-null |sellerNonce|, an [=auction +config=] |auctionConfig|, an [=auction config=]-or-null |topLevelAuctionConfig|, and a [=negative +target info=] |negativeTargetInfo|: 1. [=Assert=] that these steps are running [=in parallel=]. 1. Let |parsedSignedAdditionalBid| be the result of running [=parse a JSON string to an infra value=] @@ -4959,8 +4966,8 @@ an [=auction config=]-or-null |topLevelAuctionConfig|, and a [=negative target i 1. [=list/Append=] |signature| to |signatures|. 1. If |decodeSignatureFailed| is true, then return null. 1. Let |decodedAdditionalBid| be the result of [=decode an additional bid json=] given - |parsedSignedAdditionalBid|["bid"], |reportingContextMap|, |auctionConfig| and - |topLevelAuctionConfig|. + |parsedSignedAdditionalBid|["bid"], |reportingContextMap|, |auctionConfig|, + |topLevelAuctionConfig|, and |sellerNonce|. 1. Return null if any of the following conditions hold: * |decodedAdditionalBid| is failure; * The result of [=checking a currency tag=] with |decodedAdditionalBid|'s @@ -4980,8 +4987,8 @@ an [=auction config=]-or-null |topLevelAuctionConfig|, and a [=negative target i <div algorithm> To <dfn>decode an additional bid json</dfn> given a [=string=] |additionalBidJson|, -a [=reporting context map=] |reportingContextMap|, an [=auction config=] |auctionConfig|, and an -[=auction config=]-or-null |topLevelAuctionConfig|: +a [=reporting context map=] |reportingContextMap|, an [=auction config=] |auctionConfig|, an +[=auction config=]-or-null |topLevelAuctionConfig|, and a [=string=]-or-null |sellerNonce|: 1. [=Assert=] that these steps are running [=in parallel=]. 1. Let |parsedAdditionalBid| be the result of [=parse a JSON string to an infra value=] given @@ -4989,12 +4996,25 @@ a [=reporting context map=] |reportingContextMap|, an [=auction config=] |auctio 1. If |parsedAdditionalBid| is not a [=map=], then return failure. 1. Let |result| be a new [=decoded additional bid=]. 1. Return failure if any of the following conditions hold: - * |parsedAdditionalBid|["auctionNonce"] does not [=map/exist=]; - * |parsedAdditionalBid|["auctionNonce"] is not the [=string representation=] of - |auctionConfig|'s [=auction config/auction nonce=]; + * |parsedAdditionalBid|["auctionNonce"] [=map/exists=] and |parsedAdditionalBid|["bidNonce"] + [=map/exists=]; + * |parsedAdditionalBid|["auctionNonce"] does not [=map/exist=] and + |parsedAdditionalBid|["bidNonce"] does not [=map/exist=]; * |parsedAdditionalBid|["seller"] does not [=map/exist=]; * The result of running the [=parse an https origin=] with |parsedAdditionalBid|["seller"] is failure, or not [=same origin=] with |auctionConfig|'s [=auction config/seller=]. + 1. If |sellerNonce| is not null: + 1. Return failure if any of the following conditions hold: + 1. |parsedAdditionalBid|["bidNonce"] does not [=map/exist=]; + 1. |parsedAdditionalBid|["bidNonce"] is not the result of [=calculate expected bid nonce=] + given the [=string representation=] of |auctionConfig|'s [=auction config/auction nonce=] + and |sellerNonce|. + 1. Otherwise: + 1. Return failure if any of the following conditions hold: + * |parsedAdditionalBid|["auctionNonce"] does not [=map/exist=]; + * |parsedAdditionalBid|["auctionNonce"] is not the [=string representation=] of + |auctionConfig|'s [=auction config/auction nonce=]; + 1. If |topLevelAuctionConfig| is null: 1. If |parsedAdditionalBid|["topLevelSeller"] [=map/exists=], then return failure. 1. Otherwise: @@ -5119,6 +5139,15 @@ a [=reporting context map=] |reportingContextMap|, an [=auction config=] |auctio 1. Return |result|. </div> +<div algorithm> +To <dfn>calculate expected bid nonce</dfn> given a [=string=] |auctionNonce| and a [=string=] +|sellerNonce|: + + 1. Return the result of [=forgiving-base64 encoding=] the [=SHA-256=] hash of the result of + [=string/concatenating=] «|auctionNonce|, |sellerNonce|». + +</div> + A <dfn>signed additional bid signature</dfn> is a [=struct=] with the following [=struct/items=]: <dl dfn-for="signed additional bid signature"> : <dfn>key</dfn> @@ -5138,10 +5167,6 @@ A <dfn>decoded additional bid</dfn> is a [=struct=] with the following [=struct/ [=decoded additional bid/negative target interest group names=]. </dl> -Each [=traversable navigable=] has a <dfn>captured additional bids headers</dfn>, which is a [=map=] -whose [=map/keys=] are [=strings=] for [=auction nonces=], and whose values are [=list=] of -[=strings=] for encoded additional bids. - ## Negative Targeting ## {#negative-targeting-section} *This first introductory paragraph is non-normative.* @@ -5155,7 +5180,7 @@ Protected Audience [=auctions=], each [=additional bid=] is allowed to identify that specifies no [=negative interest groups=] is always accepted into the [=auction=]. <div algorithm> -To <dfn>check whether negative targeted</dfn> given an [=decoded additional bid=] |additionalBid|, a +To <dfn>check whether negative targeted</dfn> given a [=decoded additional bid=] |additionalBid|, a [=set=] of [=byte sequences=] |verifiedSignatureKeys|, and a [=negative target info=] |negativeTargetInfo|: @@ -5312,7 +5337,8 @@ from querying the server during an auction. To <dfn>query k-anonymity cache</dfn> 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. If the difference between [=current coarsened 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=]. </div> @@ -5414,7 +5440,7 @@ from querying the server during an auction. To <dfn>update k-anonymity cache for key</dfn> given a [=SHA-256=] |hashCode|: 1. [=Assert=] that these steps are running [=in parallel=]. 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/timestamp=] field to the [=current coarsened 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|. </div> @@ -5597,7 +5623,7 @@ of the following global objects: a {{DirectFromSellerSignalsForBuyer}} |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=]). + contributions=], [=Private Aggregation contributions=], [=execution metrics=]). 1. Let |realm| be the result of [=creating a new script runner realm=] given {{InterestGroupBiddingScriptRunnerGlobalScope}}. @@ -6495,7 +6521,7 @@ navigating to another page. Some implementations, such as Chromium, have chosen 1. [=list/For each=] |owner| of |owners|: 1. [=list/For each=] |originalInterestGroup| of the [=user agent=]'s [=interest group set=] whose [=interest group/owner=] is |owner| and [=interest group/next update after=] is before - the [=current wall time=] and whose [=interest group/update url=] is not null: + the [=current coarsened wall time=] and whose [=interest group/update url=] is not null: Note: Implementations can consider loading only a portion of these interest groups at a time to avoid issuing too many requests at once. @@ -6777,8 +6803,9 @@ navigating to another page. Some implementations, such as Chromium, have chosen * |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 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. Set |ig|'s [=interest group/next update after=] to the [=current coarsened wall time=] plus + 24 hours. + 1. Set |ig|'s [=interest group/last updated=] to the [=current coarsened wall time=]. 1. [=list/Replace=] the [=interest group=] that has |ig|'s [=interest group/owner=] and [=interest group/name=] in the [=user agent=]'s [=interest group set=] with |ig|. 1. <i id=abort-update>Abort update</i>: We jump here if some part of the @@ -6801,10 +6828,10 @@ To <dfn>process updateIfOlderThanMs</dfn> given an [=origin=] |buyer|, and an [= 1. Let |ig| be the [=interest group=] of the [=user agent=]'s [=interest group set=] whose [=interest group/owner=] is |buyer| and whose [=interest group/name=] is |igName|, or null if [=interest group set=] does not have such an interest group. - 1. If |ig| is not null and the [=current wall time=] &minus; |ig|'s + 1. If |ig| is not null and the [=current coarsened wall time=] &minus; |ig|'s [=interest group/last updated=] ≥ |updateIfOlderThan|: 1. Set |ig|'s [=interest group/next update after=] to - [=current wall time=] + |updateIfOlderThan|. + [=current coarsened wall time=] + |updateIfOlderThan|. 1. [=list/Replace=] the [=interest group=] that has |ig|'s [=interest group/owner=] and [=interest group/name=] in the [=user agent=]'s [=interest group set=] with |ig|. @@ -7137,7 +7164,7 @@ request with the <{iframe/adauctionheaders}> Each [=traversable navigable=] has a <dfn for="traversable navigable">captured ad auction additional bids headers</dfn>, which is a [=map=] whose [=map/keys=] are [=auction nonces=] and whose -[=map/values=] are [=strings=]. +[=map/values=] are [=lists=] of [=signed additional bid with metadata=]. NOTE: This is only captured during a [=request=] whose [=request/initiator type=] is `"fetch"`, made with the {{RequestInit/adAuctionHeaders}} option set to `true`, or during an @@ -7149,11 +7176,11 @@ Each [=traversable navigable=] has a <dfn for="traversable navigable">saved Bidd 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> Each [=traversable navigable=] has a <dfn for="traversable navigable">captured ad auction result headers</dfn>, which is a [=map=] whose [=map/keys=] are [=origins=] and [=map/values=] are [=strings=]. +</div> <div algorithm="fetch capture adAuctionHeaders boolean patch"> Modify the definition of a [=request=]: @@ -7244,9 +7271,12 @@ component, and top-level auctions may specify which signals to load by the `adSl HTTP response header.</h3> The \`<dfn http-header><code>Ad-Auction-Additional-Bid</code></dfn>\` response header provides value -of a string in the format of `<auction nonce>:<base64-encoding of the signed additional bid>`, which -corresponds to a single [=additional bid=]. The response may include more than one [=additional bid=] -by specifying multiple instances of the [:Ad-Auction-Additional-Bid:] response header. +of a string in the format of `<auction nonce>:<seller nonce>:<base64-encoding of the signed +additional bid>`, or the legacy format of `<auction nonce>:<base64-encoding of the signed additional +bid>`, either of which corresponds to a single [=additional bid=]. In the format that provides a +seller nonce, the seller nonce must be as described in [=signed additional bid with metadata/seller +nonce=]. The response may include more than one [=additional bid=] by specifying multiple instances +of the [:Ad-Auction-Additional-Bid:] response header. </div> <h3 id=ad-auction-result-header>The \`<a http-header><code>Ad-Auction-Result</code></a>\` @@ -7323,11 +7353,24 @@ The following algorithm will be added to the [[FETCH#fetching]] section: that is, scripts making the {{WindowOrWorkerGlobalScope/fetch()}} request aren't able to load the header value. 1. [=list/For each=] |bid| of |additionalBids|: - 1. Let |nonceAndAdditionalBid| be the result of [=strictly splitting=] |bid| on U+003A (:). - 1. If |nonceAndAdditionalBid|'s [=list/size=] is not 2, then [=iteration/continue=]. - 1. Let |nonce| be |nonceAndAdditionalBid|[0]. - 1. If |nonce|'s [=string/length=] is not 36, then [=iteration/continue=]. - 1. Set |storedAdditionalBidsHeaders|[|nonce|] to |nonceAndAdditionalBid|[1]. + 1. Let |parts| be the result of [=strictly splitting=] |bid| on U+003A (:). + 1. Let |bidWithMetadata| be a new [=signed additional bid with metadata=]. + 1. If |parts|'s [=list/size=] is 3: + 1. Let |auctionNonce| be |parts|[0]. + 1. If |auctionNonce|'s [=string/length=] is not 36, then [=iteration/continue=]. + 1. Let |sellerNonce| be |parts|[1]. + 1. If |sellerNonce|'s [=string/length=] is not 36, then [=iteration/continue=]. + 1. Let |bidWithMetadata|'s [=signed additional bid with metadata/seller nonce=] be + |sellerNonce|. + 1. Let |bidWithMetadata|'s [=signed additional bid with metadata/signed additional bid=] be + |parts|[2]. + 1. [=list/Append=] |bidWithMetadata| to |storedAdditionalBidsHeaders|[|auctionNonce|]. + 1. Otherwise, if |parts|'s [=list/size=] is 2: + 1. Let |auctionNonce| be |parts|[0]. + 1. If |auctionNonce|'s [=string/length=] is not 36, then [=iteration/continue=]. + 1. Let |bidWithMetadata|'s [=signed additional bid with metadata/signed additional bid=] be + |parts|[1]. + 1. [=list/Append=] |bidWithMetadata| to |storedAdditionalBidsHeaders|[|auctionNonce|]. 1. Let |adAuctionResults| be the result of [=header list/getting, decoding, and splitting=] [:Ad-Auction-Result:] from |responseHeaders|. @@ -8004,8 +8047,10 @@ An <dfn export>auction config</dfn> is a [=struct=] with the following [=struct/ uniquely associated with all {{AuctionAdConfig/componentAuctions}}. This must come from a prior call to {{Window/navigator}}.{{Navigator/createAuctionNonce()}}. This is only required for auctions that provide [=additional bids=], and each of those - [=additional bids=] must use the same auction nonce to ensure that each of them was intended for - this and only this auction. + [=additional bids=] must include the bid nonce derived from that auction nonce as computed by + [=calculate expected bid nonce=] to ensure that each [=additional bid=] was intended for this + and only this auction. For backwards compatibility, an [=additional bid=] may include the + [=auction nonce=] directly in place of a bid nonce. : <dfn>expects additional bids</dfn> :: A [=boolean=] or failure, initially false. Specifies whether some bids will be provided as signed exchanges. Sets to failure if the @@ -8226,6 +8271,7 @@ a [=script fetcher=] |fetcher|: 1. Set |fetcher|'s [=script fetcher/fetch duration=] to the [=duration from=] |fetchStart| to |settings|'s [=environment settings object/current monotonic time=], in milliseconds. 1. Set |fetcher|'s [=script fetcher/script body=] to failure. + 1. Return. 1. Set |fetcher|'s [=script fetcher/origins authorized for cross origin trusted signals=] to the result of [=parsing allowed trusted scoring signals origins=] given |response|'s [=response/ header list=]. @@ -8450,7 +8496,7 @@ requests into smaller number of fetches. It's a [=struct=] with the following [= : <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 + [=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 @@ -8921,6 +8967,20 @@ A <dfn>direct from seller signals</dfn> is a [=struct=] with the following [=str JSON data passed to corresponding buyer's [=script runner=]. </dl> +<h3 id=additional-bids-structs-section>Additional bids</h3> + +A <dfn>signed additional bid with metadata</dfn> is a [=struct=] with the following +[=struct/items=]: + +<dl dfn-for="signed additional bid with metadata"> + : <dfn>signed additional bid</dfn> + :: A [=string=] for an encoded additional bid. + : <dfn>seller nonce</dfn> + :: Null or a [=string=]. If present, represents the randomly-generated seller nonce returned in + the [:Ad-Auction-Additional-Bid:] response header. This must be 36 characters, and should be the + canonoical [=string=] representation of a [=version 4 UUID=]. +</dl> + <h3 id=score-ad-output-header>Score ad output</h3> The output of running a Protected Audience `scoreAd()` script, is represented using the following type: