resolveToConfig;
};
@@ -930,6 +956,29 @@ To validate and convert auction ad config given an {{AuctionAdConfig}
* To parse the value |result|, set |auctionConfig|'s [=auction config/seller signals=] to the
result of [=serializing a JavaScript value to a JSON string=], given |result|.
* To handle an error, set |auctionConfig|'s [=auction config/seller signals=] to failure.
+1. If |config|["{{AuctionAdConfig/auctionNonce}}"] [=map/exists=], then [=map/set=] |auctionConfig|'s
+
+ [=auction config/auction nonce=] to the result of running [=get uuid from string=] with
+ |config|["{{AuctionAdConfig/auctionNonce}}"].
+1. If |config|["{{AuctionAdConfig/additionalBids}}"] [=map/exists=]:
+ 1. [=exception/Throw=] a {{TypeError}} if any of the following conditions hold:
+ * |config|["{{AuctionAdConfig/auctionNonce}}"] does not [=map/exist=];
+ * |config|["{{AuctionAdConfig/interestGroupBuyers}}"] does not [=map/exist=], or is [=list/empty=];
+ * |config|["{{AuctionAdConfig/componentAuctions}}"] is not [=list/empty=].
+ 1. Set |auctionConfig|'s [=auction config/expects additional bids=] to true.
+ 1. [=Handle an input promise in configuration=] given |auctionConfig| and
+ |config|["{{AuctionAdConfig/additionalBids}}"]:
+
+ Note: The JavaScript code calling {{Navigator/runAdAuction()}} is responsible for not resolving
+ |config|["{{AuctionAdConfig/additionalBids}}"] until an associated [=request=] whose
+ [=request/initiator type=] is `"fetch"` and the {{RequestInit/adAuctionHeaders}} option set to
+ `true` returns a response, or alternatively, not calling {{Navigator/runAdAuction()}} until the
+ [=request=] returns a response.
+
+ * To parse the value |result|:
+ 1. Set |auctionConfig|'s [=auction config/expects additional bids=] to false.
+ * To handle an error:
+ 1. Set |auctionConfig|'s [=auction config/expects additional bids=] to failure.
1. If |config|["{{AuctionAdConfig/directFromSellerSignalsHeaderAdSlot}}"] [=map/exists=]:
1. [=Handle an input promise in configuration=] given |auctionConfig| and
|config|["{{AuctionAdConfig/directFromSellerSignalsHeaderAdSlot}}"]:
@@ -1107,9 +1156,17 @@ To parse an https origin given a [=string=] |input|:
To build bid generators map given an [=auction config=] |auctionConfig|:
1. Let |bidGenerators| be a new [=ordered map=] whose [=map/keys=] are [=origins=] and whose
[=map/values=] are [=per buyer bid generators=].
+1. Let |negativeTargetInfo| be a new [=negative target info=].
1. [=list/For each=] |buyer| in |auctionConfig|'s [=auction config/interest group buyers=]:
1. [=list/For each=] |ig| of the user agent's [=interest group set=] whose
[=interest group/owner=] is |buyer|:
+ 1. Let |igName| be |ig|'s [=interest group/name=].
+ 1. If |ig|'s [=interest group/additional bid key=] is not null:
+ 1. [=map/Set=] |negativeTargetInfo|[(|buyer|, |igName|)] to (|ig|'s
+ [=interest group/joining origin=], |ig|'s [=interest group/additional bid key=]).
+ 1. [=iteration/Continue=] if any of the following conditions hold:
+ * |ig|'s [=interest group/bidding url=] is null;
+ * |ig|'s [=interest group/ads=] is null, or [=list/is empty=].
1. Let |signalsUrl| be |ig|'s [=interest group/trusted bidding signals url=].
1. Let |joiningOrigin| be |ig|'s [=interest group/joining origin=].
1. If |bidGenerators| does not [=map/contain=] |buyer|:
@@ -1131,7 +1188,7 @@ To build bid generators map given an [=auction config=] |auctionConfi
1. If |perSignalsUrlGenerator| does not [=map/contain=] |joiningOrigin|, then [=map/set=]
|perSignalsUrlGenerator|[|joiningOrigin|] to « |ig| ».
1. Otherwise, [=list/append=] |ig| to |perSignalsUrlGenerator|[|joiningOrigin|].
-1. Return |bidGenerators|.
+1. Return « |bidGenerators|, |negativeTargetInfo| ».
@@ -1190,7 +1247,8 @@ To generate and score bids given an [=auction config=] |auctionConfig
1. Let |decisionLogicScript| be the result of [=fetching script=] with |auctionConfig|'s
[=auction config/decision logic url=].
1. If |decisionLogicScript| is failure, return null.
-1. Let |bidGenerators| be the result of running [=build bid generators map=] with |auctionConfig|.
+1. Let « |bidGenerators|, |negativeTargetInfo| » be the result of running
+ [=build bid generators map=] with |auctionConfig|.
1. Let |leadingBidInfo| be a new [=leading bid info=].
1. Let |queue| be the result of [=starting a new parallel queue=].
1. Let |capturedAuctionHeaders| be |global|'s [=associated Document's=] [=node navigable's=]
@@ -1276,6 +1334,14 @@ To generate and score bids given an [=auction config=] |auctionConfig
1. Set |componentAuctionExpectedCurrency| to the result of [=looking up per-buyer currency=] with
|topLevelAuctionConfig| and |auctionConfig|'s [=auction config/seller=].
1. Let |pendingBuyers| be the [=map/size=] of |bidGenerators|.
+1. Let |additionalBids| be the result of running [=validate and convert additional bids=] with
+ |auctionConfig|, |topLevelAuctionConfig|, |negativeTargetInfo| and |global|.
+1. Let |pendingAdditionalBids| be the [=list/size=] of |additionalBids|.
+1. [=list/For each=] |additionalBid| of |additionalBids|, run the following steps [=in parallel=]:
+ 1. [=Score and rank a bid=] with |auctionConfig|, |additionalBid|, |leadingBidInfo|,
+ |decisionLogicScript|, null, |auctionLevel|, |componentAuctionExpectedCurrency|, and
+ |settings|'s [=environment/top-level origin=].
+ 1. Decrement |pendingAdditionalBids| by 1.
1. [=map/For each=] |buyer| → |perBuyerGenerator| of |bidGenerators|,
[=parallel queue/enqueue steps|enqueue the following steps=] to |queue|:
1. Let |buyerExperimentGroupId| be |allBuyersExperimentGroupId|.
@@ -1371,7 +1437,7 @@ To generate and score bids given an [=auction config=] |auctionConfig
|decisionLogicScript|, |directFromSellerSignalsForSeller|, |dataVersion|, |auctionLevel|,
|componentAuctionExpectedCurrency|, and |settings|'s [=environment/top-level origin=].
1. Decrement |pendingBuyers| by 1.
-1. Wait until |pendingBuyers| is 0.
+1. Wait until both |pendingBuyers| and |pendingAdditionalBids| are 0.
1. If |leadingBidInfo|'s [=leading bid info/leading bid=] is null, return null.
1. If |topLevelAuctionConfig| is null:
1. Let « |sellerSignals|, |reportResultBrowserSignals| » be the result of running
@@ -1615,7 +1681,7 @@ The Ad-Auction-Allowed
HTTP response header
To validate fetching response given a [=response=] |response|, null, failure, or a
-[=byte sequence=]|responseBody|, and a [=string=] |mimeType|:
+[=byte sequence=] |responseBody|, and a [=string=] |mimeType|:
1. If |responseBody| is null or failure, return false.
1. If [=header list/getting a structured field value|getting=] [:Ad-Auction-Allowed:] and
@@ -2074,6 +2140,9 @@ a {{ReportingBrowserSignals}} |browserSignals|, and a [=direct from seller signa
to |winner|'s [=generated bid/interest group=] [=interest group/name=].
1. Let |buyerReportingScript| be the result of [=fetching script=] with |winner|'s
[=generated bid/interest group=]'s [=interest group/bidding url=].
+ 1. Let |reportFunctionName| be "`reportWin`".
+ 1. If |winner|'s [=generated bid/provided as additional bid=] is true:
+ 1. Set |reportFunctionName| be "`reportAdditionalBidWin`".
1. Let « ignored, |resultUrl|, |reportingBeaconMap|, |reportingMacroMap| » be the result of
[=evaluating a reporting script=] with |buyerReportingScript|, "`reportWin`", and
« |leadingBidInfo|'s [=leading bid info/auction config=]'s [=auction config/config idl=]'s
@@ -2091,6 +2160,411 @@ a {{ReportingBrowserSignals}} |browserSignals|, and a [=direct from seller signa
:: |reportingMacroMap|
+# Additional Bids and Negative Targeting # {#additional-bids-and-negative-targeting}
+
+## createAuctionNonce() ## {#create-auction-nonce}
+
+*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.
+
+
+[SecureContext]
+partial interface Navigator {
+ Promise createAuctionNonce();
+};
+
+
+
+The
createAuctionNonce() method steps are:
+
+ 1. Let |p| be [=a new promise=].
+ 1. Run the following steps [=in parallel=]:
+ 1. Let |nonce| be the [=string representation=] of a [=version 4 UUID=].
+
+
+ Because we're going in parallel:
+ * There is no guarantee that the promise will be resolved before other tasks get queued on the
+ main thread;
+ * ...which gives browsers the freedom to generate this UUID in another process, and
+ asynchronously send it back to the main thread at an arbitrary future time.
+
+ 1. Let |global| be [=this=]'s [=relevant global object=].
+ 1. [=Queue a global task=] on [=DOM manipulation task source=], given |global|, to [=resolve=]
+ |p| with |nonce|.
+ 1. Return |p|.
+
+
+## Additional Bids ## {#additional-bids-section}
+
+*This first introductory paragraph is non-normative.*
+
+In addition to [=generate a bid|bids generated by interest groups=], sellers can enable buyers to
+introduce bids generated outside of the auction, which are called additional bids.
+[=Additional bids=] are commonly triggered using contextual signals. Buyers compute the
+[=additional bids=], likely as part of a contextual auction. Buyers need to package up each
+[=additional bid=] using a new data structure that encapsulates all of the information needed for
+the [=additional bid=] to compete against other bids in a Protected Audience [=auction=].
+
+
+
Each additional bid is expressed using the following JSON data structure:
+
+ const additionalBid = {
+ "bid": {
+ "ad": 'ad-metadata',
+ "adCost": 2.99,
+ "bid": 1.99,
+ "bidCurrency": "USD",
+ "render": "https://www.example-dsp.com/ad/123.jpg",
+ "adComponents": [adComponent1, adComponent2],
+ "allowComponentAuction": true,
+ "modelingSignals": 123,
+ },
+ "interestGroup": {
+ "owner": "https://www.example-dsp.com"
+ "name": "campaign123",
+ "biddingLogicURL": "https://www.example-dsp.com/bid_logic.js"
+ },
+ "negativeInterestGroups": {
+ joiningOrigin: "https://www.example-advertiser.com",
+ interestGroupNames: [
+ "example_advertiser_negative_interest_group_a",
+ "example_advertiser_negative_interest_group_b",
+ ]
+ },
+ "auctionNonce": "12345678-90ab-cdef-fedcba09876543210",
+ "seller": "https://www.example-ssp.com",
+ "topLevelSeller": "https://www.another-ssp.com"
+ }
+
+
+
+
+To validate and convert additional bids given an [=auction config=] |auctionConfig|, an
+[=auction config=]-or-null |topLevelAuctionConfig|, a [=negative target info=] |negativeTargetInfo|,
+and a [=global object=] |global|:
+
+ 1. [=Assert=] that these steps are running [=in parallel=].
+ 1. [=Assert=] that |auctionConfig|'s [=auction config/auction nonce=] is not null.
+ 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=].
+ 1. Let |additionalBids| be a new [=list=] of [=decoded additional bids=].
+ 1. [=list/For each=] |encodedSignedAdditionalBid| of |capturedAdditionalBidsHeaders|[|auctionNonce|]:
+ 1. Let |signedAdditionalBid| be the result of running [=forgiving-base64 decode=] with
+ |encodedSignedAdditionalBid|.
+ 1. If |signedAdditionalBid| is failure, then [=iteration/continue=].
+ 1. Let |additionalBid| be the result of running [=parse a signed additional bid=] given
+ |signedAdditionalBid|, |auctionConfig|, |topLevelAuctionConfig|, and |negativeTargetInfo|.
+ 1. If |additionalBid| is not null, then [=list/append=] |additionalBid| to |additionalBids|.
+ 1. Return |additionalBids|.
+
+
+
+To parse a signed additional bid given a [=byte sequence=] |signedAdditionalBid|, 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=]
+ given |signedAdditionalBid|.
+ 1. Return null if any of the following conditions hold:
+ * |parsedSignedAdditionalBid| is not a [=map=];
+ * |parsedSignedAdditionalBid|["bid"] does not [=map/exist=], or is not a [=string=];
+ * |parsedSignedAdditionalBid|["signatures"] does not [=map/exist=], or is not a [=list=].
+ 1. Let |signatures| be a new [=list=] of [=signed additional bid signatures=].
+ 1. Let |decodeSignatureFailed| be false.
+ 1. [=list/For each=] |sig| of |parsedSignedAdditionalBid|["signatures"]:
+ 1. Set |decodeSignatureFailed| to true and [=iteration/break=] if any of the following
+ conditions hold:
+ * |sig| is not a [=map=];
+ * |sig|["key"] does not [=map/exist=], or is not a [=string=];
+ * |sig|["signature"] does not [=map/exist=], or is not a [=string=].
+ 1. Let |maybeKey| be the result of running [=forgiving-base64 decode=] with |sig|["key"].
+ 1. Let |maybeSignature| be the result of running [=forgiving-base64 decode=] with
+ |sig|["signature"].
+ 1. Set |decodeSignatureFailed| to true and [=iteration/break=] if any of the following
+ conditions hold:
+ * |maybeKey| is failure, or its [=byte sequence/length=] is not 32;
+ * |maybeSignature| is failure, or its [=byte sequence/length=] is not 64;
+ 1. Let |signature| be a [=signed additional bid signatures=], whose
+ [=signed additional bid signature/key=] is |maybeKey|, and
+ [=signed additional bid signature/signature=] is |maybeSignature|.
+ 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"], |auctionConfig| and |topLevelAuctionConfig|.
+ 1. Return null if any of the following conditions hold:
+ * |decodedAdditionalBid| is failure;
+ * The result of [=checking a currency tag=] with |decodedAdditionalBid|'s
+ [=decoded additional bid/bid=]'s [=generated bid/bid=]'s [=bid with currency/currency=] and
+ the result of running [=look up per-buyer currency=] with |auctionConfig|.
+ 1. Let |verifiedSignatureKeys| be a new [=set=] of [=byte sequences=].
+ 1. [=list/For each=] |signature| of |signatures|:
+ 1. Let |isSignatureValid| be the result of running [=verify=] |signature|'s
+ [=signed additional bid signature/signature=] on message |parsedSignedAdditionalBid|["bid"]
+ using |signature|'s [=signed additional bid signature/key=], with 0 for Ed25519ctx.
+ 1. If |isSignatureValid| is true, then [=set/append=] |signature|'s
+ [=signed additional bid signature/key=] to |verifiedSignatureKeys|.
+ 1. If the result of [=checking whether negative targeted=] given |decodedAdditionalBid|,
+ |verifiedSignatureKeys| and |negativeTargetInfo| is true, then return null.
+ 1. Return |decodedAdditionalBid|.
+
+
+
+To decode an additional bid json given a [=string=] |additionalBidJson|, an
+[=auction config=] |auctionConfig|, and an [=auction config=]-or-null |topLevelAuctionConfig|:
+
+ 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
+ |additionalBidJson|.
+ 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|["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 |topLevelAuctionConfig| is null:
+ 1. If |parsedAdditionalBid|["topLevelSeller"] [=map/exists=], then return failure.
+ 1. Otherwise:
+ 1. If |parsedAdditionalBid|["topLevelSeller"] does not [=map/exist=], then return failure.
+ 1. Let |bidTopLevelSeller| be the result of running the [=parse an https origin=] with
+ |parsedAdditionalBid|["topLevelSeller"].
+ 1. If |bidTopLevelSeller| is failure, or |bidTopLevelSeller| is not [=same origin=] with
+ |topLevelAuctionConfig|'s [=auction config/seller=], then return failure.
+ 1. If |parsedAdditionalBid|["interestGroup"] does not [=map/exist=], then return failure.
+ 1. Let |igMap| be |parsedAdditionalBid|["interestGroup"].
+ 1. Return failure if any the following conditions hold:
+ * |igMap| is not a [=map=];
+ * |igMap|["name"] does not [=map/exist=], or is not a [=string=];
+ * |igMap|["biddingLogicURL"] does not [=map/exist=], or is not a [=string=];
+ * |igMap|["owner"] does not [=map/exist=], or is not a [=string=];
+ 1. Let |igOwner| be the result of running [=parse an https origin=] given |igMap|["owner"].
+ 1. Let |igName| be |igMap|["name"].
+ 1. Let |igBiddingUrl| be the result of running [=url parser=] on |igMap|["biddingLogicURL"].
+ 1. Return failure if any of the following conditions hold:
+ * |igOwner| is failure;
+ * |auctionConfig|'s [=auction config/interest group buyers=] does not [=list/contain=] |igOwner|;
+ * |igBiddingUrl| is failure;
+ * |igOwner| is not [=same origin=] with |igBiddingUrl|.
+ 1. Let |ig| be a new [=interest group=] with the following properties:
+ : [=interest group/owner=]
+ :: |igOwner|
+ : [=interest group/name=]
+ :: |igName|
+ : [=interest group/bidding url=]
+ :: |igBiddingUrl|
+ 1. If |parsedAdditionalBid|["bid"] does not [=map/exist=], or is not a [=map=], return failure.
+ 1. Let |bidMap| be |parsedAdditionalBid|["bid"].
+ 1. If |bidMap|["render"] does not [=map/exist=] or is not a [=string=], then return failure.
+ 1. Let |renderUrl| be the result of running [=URL parser=] on |bidMap|["render"].
+
+ 1. If |renderUrl| is failure, then return failure.
+ 1. Let |ad| be a new [=interest group ad=] whose [=interest group ad/render url=] is |renderUrl|.
+ 1. Set |ig|'s [=interest group/ads=] to « |ad| ».
+ 1. Let |bidVal| be |bidMap|["bid"] if it [=map/exists=], otherwise return failure.
+ 1. If |bidVal| is not a {{double}}, or is less than or equal to 0, then return failure.
+ 1. Let |adMetadata| be "null".
+ 1. If |bidMap|["ad"] [=map/exists=]:
+ 1. Set |adMetadata| to the result of running [=serialize an Infra value to a JSON string=] with
+ |bidMap|["ad"].
+ 1. Let |bidCurrency| be null.
+ 1. If |bidMap|["bidCurrency"] [=map/exists=]:
+ 1. If |bidMap|["bidCurrency"] is not a [=string=], or the result of [=checking whether a string
+ is a valid currency tag=] is failure, then return failure.
+ 1. Set |bidCurrency| to |bidMap|["bidCurrency"].
+ 1. Let |adCost| be null.
+ 1. If |bidMap|["adCost"] [=map/exists=]:
+ 1. If |bidMap|["adCost"] is not a {{double}}, then return failure.
+ 1. Set |adCost| to |bidMap|["adCost"].
+ 1. Let |modelingSignals| be null.
+ 1. If |bidMap|["modelingSignals"] [=map/exists=]:
+ 1. If |bidMap|["modelingSignals"] is not a {{double}}, then return failure.
+ 1. If |bidMap|["modelingSignals"] ≥ 0, and < 4096, then set |modelingSignals| to
+ |bidMap|["modelingSignals"].
+ 1. Let |adComponents| be a new [=list=] of [=ad descriptors=].
+ 1. If |bidMap|["adComponents"] [=map/exists=]:
+ 1. If |bidMap|["adComponents"] is not a [=list=], then return failure.
+ 1. [=list/For each=] |component| of |bidMap|["adComponents"]:
+ 1. If |component| is not a [=string=], then return failure.
+ 1. Let |componentUrl| be the result of running [=URL parser=] on |component|.
+ 1. If |componentUrl| is failure, then return failure.
+ 1. Let |componentDescriptor| be a new [=ad descriptor=] whose [=ad descriptor/url=] is
+ |componentUrl|.
+ 1. [=list/Append=] |componentDescriptor| to |adComponents|.
+ 1. Set |ig|'s [=interest group/ad components=] to |adComponents|.
+ 1. If |parsedAdditionalBid|["negativeInterestGroup"] [=map/exists=]:
+ 1. If |parsedAdditionalBid|["negativeInterestGroups"] [=map/exists=], or
+ |parsedAdditionalBid|["negativeInterestGroup"] is not a [=string=], then return failure.
+ 1. [=list/Append=] |parsedAdditionalBid|["negativeInterestGroup"] to |result|'s
+ [=decoded additional bid/negative target interest group names=].
+ 1. If |parsedAdditionalBid|["negativeInterestGroups"] [=map/exists=]:
+ 1. Let |multipleNegativeIg| be |parsedAdditionalBid|["negativeInterestGroups"].
+ 1. Return failure if any of the following conditions hold:
+ * |multipleNegativeIg| is not a [=map=];
+ * |multipleNegativeIg|["joiningOrigin"] does not [=map/exist=], or is not a [=string=];
+ * |multipleNegativeIg|["interestGroupNames"] does not [=map/exist=], or is not a [=list=].
+ 1. Let |joiningOrigin| be the result of running [=parse an https origin=] with
+ |multipleNegativeIg|["joiningOrigin"].
+ 1. If |joiningOrigin| is failure, then return failure.
+ 1. Set |result|'s [=decoded additional bid/negative target joining origin=] to |joiningOrigin|.
+ 1. [=list/For each=] |igName| of |multipleNegativeIg|["interestGroupNames"]:
+ 1. If |igName| is not a [=string=], then return failure.
+ 1. [=list/Append=] |igName| to |result|'s
+ [=decoded additional bid/negative target interest group names=].
+ 1. Set |result|'s [=decoded additional bid/bid=] to a new [=generated bid=] with the following
+ properties:
+ : [=generated bid/bid=]
+ :: A [=bid with currency=] whose [=bid with currency/value=] is |bidVal|, and
+ [=bid with currency/currency=] is |bidCurrency|
+ : [=generated bid/ad=]
+ :: |adMetadata|
+ : [=generated bid/ad descriptor=]
+ :: An [=ad descriptor=] whose [=ad descriptor/url=] is |renderUrl|
+ : [=generated bid/ad component descriptors=]
+ :: |adComponents|
+ : [=generated bid/ad cost=]
+ :: |adCost|
+ : [=generated bid/modeling signals=]
+ :: |modelingSignals|
+ : [=generated bid/interest group=]
+ :: |ig|
+ : [=generated bid/bid ad=]
+ :: A [=interest group ad=] whose [=interest group ad/render url=] is |renderUrl|, and
+ [=interest group ad/metadata=] is |adMetadata|
+ : [=generated bid/provided as additional bid=]
+ :: true
+ 1. Return |result|.
+
+
+A signed additional bid signature is a [=struct=] with the following [=struct/items=]:
+
+ : key
+ :: A [=byte sequence=] of length 32.
+ : signature
+ :: A [=byte sequence=] of length 64.
+
+
+A decoded additional bid is a [=struct=] with the following [=struct/items=]:
+
+ : bid
+ :: A [=generated bid=]. Fields analogous to those returned by `generateBid()`.
+ : negative target interest group names
+ :: A [=list=] of [=strings=].
+ : negative target joining origin
+ :: Null or an [=origin=]. Required if there is more than one entry in
+ [=decoded additional bid/negative target interest group names=].
+
+
+Each [=traversable navigable=] has a captured additional bids headers, 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.*
+
+In online ad auctions for ad space, it’s sometimes useful to prevent showing an ad to certain
+audiences, a concept known as negative targeting. To facilitate [=negative targeting=] in
+Protected Audience [=auctions=], each [=additional bid=] is allowed to identify one or more
+[=negative interest groups=]. If the user has been joined to any of the identified
+[=negative interest groups=], the [=additional bid=] is dropped; otherwise it participates in the
+[=auction=], competing alongside bids created by calls to `generateBid()`. An [=additional bid=]
+that specifies no [=negative interest groups=] is always accepted into the [=auction=].
+
+
+To check whether negative targeted given an [=decoded additional bid=] |additionalBid|, a
+[=set=] of [=byte sequences=] |verifiedSignatureKeys|, and a [=negative target info=]
+|negativeTargetInfo|:
+
+ 1. [=Assert=] that these steps are running [=in parallel=].
+ 1. Let |negativeTargeted| be false.
+ 1. Let |additionalBidBuyer| be |additionalBid|'s [=decoded additional bid/bid=]'s
+ [=generated bid/interest group=]'s [=interest group/owner=].
+ 1. [=list/For each=] |igName| of |additionalBid|'s
+ [=decoded additional bid/negative target interest group names=]:
+ 1. If |negativeTargetInfo|[(|additionalBidBuyer|, |igName|)] [=map/exists=]:
+ 1. Let (|joiningOrigin|, |additionalBidKey|) be
+ |negativeTargetInfo|[(|additionalBidBuyer|, |igName|)].
+ 1. If |verifiedSignatureKeys| [=list/contains=] |additionalBidKey|:
+ 1. If |joiningOrigin| is not null:
+ 1. If |joiningOrigin| is not [=same origin=] with |additionalBid|'s
+ [=decoded additional bid/negative target joining origin=], then [=iteration/continue=].
+ 1. Set |negativeTargeted| to true, and [=iteration/break=].
+
+ Note: If the signature doesn't verify successfully, the [=additional bid=] proceeds as if the
+ [=negative interest group=] is not present. This ensures that only the
+ [=interest group/owner=] of the [=negative interest group=], who created the
+ {{AuctionAdInterestGroup/additionalBidKey}}, is allowed to
+ [=negative targeting|negatively target=] the interest group, and that nobody else can learn
+ whether the [=interest group set=] [=list/contains=] the interest group.
+ 1. Return |negativeTargeted|.
+
+
+A negative target info is a [=map=]. Its [=map/keys=] are [=tuples=] consisting of an
+[=origin=] for [=interest group/owner=] and a [=string=] for [=interest group/name=].
+Its [=map/values=] are [=tuples=] consisting of an [=origin=] for [=interest group/joining origin=]
+and a [=byte sequence=] for [=interest group/additional bid key=].
+
+### Negative Interest Groups ### {#negative-interest-groups}
+
+*This section is non-normative.*
+
+Though [=negative interest groups=] are joined using the same {{Navigator/joinAdInterestGroup()}}
+API as [=regular interest groups=], they remain distinct from one another. Only
+[=negative interest groups=]'s [=interest group/additional bid key=] can be non-null, while only
+[=regular interest groups=]'s [=interest group/ads=] can be non-null. Because the subset of fields
+used by a [=negative interest group=] cannot be meaningfully updated, a [=negative interest group=]'s
+[=interest group/update url=] must be null, otherwise a {{TypeError}} will be [=exception/thrown=] by
+{{Navigator/joinAdInterestGroup()}} API.
+
+[=Additional bids=] specify the [=negative interest groups=] they're [=negatively targeting=]
+against using at most one of the following two fields in their JSON data structure:
+ * negativeInterestGroup, for a single negative interest group;
+ * negativeInterestGroups, for more than one negative interest groups.
+
+If an [=additional bid=] needs to specify more than one [=negative interest groups=], all of those
+[=negative interest groups=] must be joined from the [=same origin=], and that [=origin=] must be
+identified ahead of time in the [=additional bid=]'s `joiningOrigin` field. Any
+[=negative interest group=] that wasn't joined from that identified [=origin=] is ignored for
+[=negative targeting=].
+
+
+
Use `negativeInterestGroup` in additional bid's JSON:
+
+ const additionalBid = {
+ ...
+ "negativeInterestGroup": "example_advertiser_negative_interest_group",
+ ...
+ }
+
+
Use `negativeInterestGroups` in additional bid's JSON:
+
+ const additionalBid = {
+ ...
+ "negativeInterestGroups": {
+ joiningOrigin: "https://example-advertiser.com",
+ interestGroupNames: [
+ "example_advertiser_negative_interest_group_a",
+ "example_advertiser_negative_interest_group_b",
+ ]
+ },
+ ...
+ }
+
+
+
# K-anonymity # {#k-anonymity}
Two goals of this specification rely on applying [=k-anonymity=] thresholds:
@@ -2436,7 +2910,7 @@ of the following global objects:
1. Let |reportURL| be |global|'s [=InterestGroupReportingScriptRunnerGlobalScope/report url=]
1. If |reportURL| is failure, set |reportURL| to null.
1. Let |macroMap| be |global|'s [=InterestGroupReportingScriptRunnerGlobalScope/reporting macro map=]
- if |functionName| is "`reportWin`", null otherwise.
+ if |functionName| is "`reportWin`" or "`reportAdditionalBidWin`", null otherwise.
1. Return « |resultJSON|, |reportURL|,
|global|'s [=InterestGroupReportingScriptRunnerGlobalScope/reporting beacon map=], |macroMap| ».
@@ -2822,6 +3296,8 @@ Each {{InterestGroupReportingScriptRunnerGlobalScope}} has a
# Interest Group Updates # {#interest-group-updates}
+*This first introductory paragraph is non-normative.*
+
[=Interest groups=] have an [=interest group/update url=] field that allows updating the
interest group definition stored on disk with information periodically retrieved from the
[=interest group/update url=]. The [=interest group update=] steps are triggered during
@@ -2988,6 +3464,7 @@ The updateAdInterestGroups() method steps are:
1. If |key| is not |groupMember|, [=iteration/continue=].
1. If |value| is not a [=list=] of {{AuctionAd}}, jump to the step
labeled Abort update.
+ 1. Let |igAds| be a new [=list=] of [=interest group ads=].
1. [=list/For each=] |ad| of |value|:
1. Let |igAd| be a new [=interest group ad=].
1. Let |renderURL| be the result of running the [=URL parser=] on
@@ -3007,21 +3484,58 @@ The updateAdInterestGroups() method steps are:
1. If |ad|["{{AuctionAd/buyerReportingId}}"] [=map/exists=] then set
|igAd|'s [=interest group ad/buyer reporting ID=] to it.
1. If |ad|["{{AuctionAd/buyerAndSellerReportingId}}"] [=map/exists=]
- then set |igAd|'s [=interest group ad/buyer and seller
- reporting ID=] to it.
- 1. [=list/Append=] |igAd| to |ig|'s |interestGroupField|.
+ then set |igAd|'s [=interest group ad/buyer and seller reporting ID=] to it.
+ 1. [=list/Append=] |igAd| to |igAds|.
+ 1. If |igAds| is not [=list/is empty=]:
+ 1. Set |ig|'s |interestGroupField| to |igAds|.
-
+ "`additionalBidKey`"
+
+ 1. Let |decodedKey| be the result of running [=forgiving-base64 decode=] with |value|.
+ 1. Jump to the step labeled Abort update if any of the
+ following conditions hold:
+ * |decodedKey| is a failure;
+ * |decodedKey|'s [=byte sequence/length=] is not 32;
+ 1. Set |ig|'s [=interest group/additional bid key=] to |decodedKey|.
+
+ 1. Jump to the step labeled Abort update if any of the
+ 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.
1. Set |ig|'s [=interest group/next update after=] to the [=current wall time=] plus 24 hours.
1. [=list/Replace=] the [=interest group=] that has |ig|'s [=interest group/owner=] and
[=interest group/name=] in the browser’s [=interest group set=] with |ig|.
1. Abort update: We jump here if some part of the
- [=interest group=] update failed. [=iteration/Continue=] to the next [=interest group=] update.
+ [=interest group=] update failed. [=iteration/Continue=] to the next [=interest group=] update.
+# Common Algorithms # {#common-algorithms}
+
+The following algorithms are some helper methods used in this document.
+
+
+ To get uuid from string given a [=string=] |input|:
+
+ 1. If |input|'s [=string/length=] is not 36, return an empty [=string=].
+ 1. Let |uuidStr| be an empty [=string=].
+ 1. [=list/For each=] |i| in [=the range=] from 1 to 36, inclusive:
+ 1. Let |unit| be |input|'s |i|th [=code unit=].
+ 1. If « 8, 13, 18, 23 » [=list/contains=] |i|:
+ 1. If |unit| is not 0x002D (-):
+
+ 1. Return an empty [=string=].
+ 1. Otherwise:
+ 1. If |unit| is not an [=ASCII lower hex digit=]:
+ 1. Return an empty [=string=].
+ 1. Append |unit| to the end of |uuidStr|.
+ 1. Return [=ASCII encoded=] |uuidStr|.
+
+
+
# Permissions Policy integration # {#permissions-policy-integration}
This specification defines two [=policy-controlled features=] identified by the strings
@@ -3145,6 +3659,14 @@ response header
The \`Ad-Auction-Signals
\` response header provides value
of a JSON array of dictionaries, each with an `adSlot` key. Protected Audience non-component,
component, and top-level auctions may specify which signals to load by the `adSlot` key.
+
+
+
+The \`Ad-Auction-Additional-Bid
\` response header provides value
+of a string in the format of `:`, which
+corresponds to a single additional bid. The response may include more than one additional bid by
+specifying multiple instances of the [:Ad-Auction-Additional-Bid:] response header.
@@ -3433,7 +3955,8 @@ An interest group is a [=struct=] with the following [=struct/items=]:
:
update url
:: Null or a [=URL=]. Provides a mechanism for the group's owner to periodically update the
- attributes of the interest group. See [interest group updates](#interest-group-updates).
+ attributes of the interest group. See [[#interest-group-updates]]. Must be null if
+ [=interest group/additional bid key=] is not null.
When non-null, the [=interest group/update url=]'s [=origin=] will always be [=same origin=]
with [=interest group/owner=].
@@ -3451,11 +3974,16 @@ An interest group is a [=struct=] with the following [=struct/items=]:
:: Null or a [=string=]. Additional metadata that the owner can use during on-device bidding.
: ads
:: Null or a [=list=] of [=interest group ad=]. Contains various ads that the interest group might
- show.
+ show. Must be null if [=interest group/additional bid key=] is not null.
: ad components
:: Null or a [=list=] of [=interest group ad=]. Contains various ad components (or "products") that
can be used to construct ads composed of multiple pieces — a top-level ad template "container"
which includes some slots that can be filled in with specific "products".
+: additional bid key
+:: Null or a [=byte sequence=] of length 32. Must be null if [=interest group/ads=] or
+ [=interest group/update url=] is not null. The Ed25519 public key (a 256-bit EdDSA public key)
+ used to guarantee that this [=interest group=], if used by an additional bid for a negative
+ targeting, can only be used by its [=interest group/owner=].
: joining origin
:: An [=origin=]. The top level page origin from where the interest group was joined.
: join counts
@@ -3478,6 +4006,12 @@ An interest group is a [=struct=] with the following [=struct/items=]:
+A regular interest group is an [=interest group=] whose
+[=interest group/additional bid key=] is null.
+
+A negative interest group is an [=interest group=] whose
+[=interest group/additional bid key=] is not null.
+
Interest group ad
An interest group ad is a [=struct=] with the following [=struct/items=]:
@@ -3627,10 +4161,12 @@ An auction config is a [=struct=] with the following items:
Optional identifier for an experiment group to support coordinated experiments with buyers'
trusted servers for buyers without a specified experiment group.
:
pending promise count
-:: An integer, initially 0. The number of [=auction config/auction signals=],
+:: An integer, initially 0. The number of things that are pending that are needed to score
+ everything. It includes waiting for {{Promise}}s [=auction config/auction signals=],
[=auction config/per buyer signals=], [=auction config/per buyer currencies=],
[=auction config/per buyer timeouts=], [=auction config/direct from seller signals header ad slot=],
- or [=auction config/seller signals=] whose {{Promise}}s are not yet resolved.
+ [=auction config/seller signals=], or {{AuctionAdConfig/additionalBids}} whose {{Promise}}s are not
+ yet resolved.
:
config idl
:: {{AuctionAdConfig}}.
:
resolve to config
@@ -3639,19 +4175,29 @@ An auction config is a [=struct=] with the following items:
:
seller currency
:: A [=currency tag=]. Specifies the currency bids returned by `scoreAd()` are expected to use, and
which reporting for this auction will agree on.
-
:
per buyer currencies
:: A {{Promise}} or failure or an [=ordered map=] whose [=map/keys=] are [=origins=] and whose
[=map/values=] are [=currency tags=]. Specifies the currency bids returned by `generateBid()` or
`scoreAd()` in component auctions are expected to use. The initial value is an empty map.
-
:
all buyers currency
:: A [=currency tag=]. Specifies the currency bids returned by `generateBid()` or `scoreAd()` in
component auctions are expected to use if [=auction config/per buyer currencies=] does not specify
a particular value.
-
:
direct from seller signals header ad slot
:: Null, a [=string=], a {{Promise}}, or failure. Initially null.
+:
auction nonce
+:: Null or a [=version 4 UUID=], initially null.
+ A unique identifier associated with this and only this invocation of
+ {{Window/navigator}}.{{Navigator/runAdAuction()}}. For
+ multi-seller auctions, this ID is uniquely associated with all {{AuctionAdConfig/componentAuctions}}.
+ This must come from a prior call to {{Window/navigator}}.{{Navigator/createAuctionNonce()}}. This
+ is only required for auctions that provide additional bids, and each of those additional bids must
+ use the same auction nonce to ensure that each of those additional bids was intended for this and
+ only this auction.
+:
expects additional bids
+:: A [=boolean=] or failure, initially false.
+ Specifies whether some bids will be provided as signed exchanges. Sets to failure if the
+ {{AuctionAdConfig/additionalBids}} {{Promise}} is [=rejected=].
@@ -3661,7 +4207,7 @@ To
wait until configuration input promises resolve given an [=auction
1. [=Assert=] |auctionConfig|'s [=auction config/auction signals=], [=auction config/seller signals=],
[=auction config/per buyer signals=], [=auction config/per buyer currencies=],
[=auction config/per buyer timeouts=], and [=auction config/direct from seller signals header ad slot=]
- are not {{Promise}}s.
+ are not {{Promise}}s, and [=auction config/expects additional bids=] is false.
1. If |auctionConfig|'s [=auction config/auction signals=], [=auction config/seller signals=],
[=auction config/per buyer signals=], [=auction config/per buyer currencies=],
[=auction config/per buyer timeouts=], or [=auction config/direct from seller signals header ad slot=]
@@ -3674,8 +4220,8 @@ To
wait until configuration input promises resolve given an [=auction
To
recursively wait until configuration input promises resolve given an [=auction config=]
|auctionConfig|:
1. [=list/For each=] |componentAuctionConfig| in |auctionConfig|'s [=auction config/component auctions=]:
- 1. If the result of [=waiting until configuration input promises resolve=] given
- |componentAuctionConfig| is failure, return failure.
+ 1. If the result of [=waiting until configuration input promises resolve=] given |componentAuctionConfig| is
+ failure, return failure.
1. Return the result of [=waiting until configuration input promises resolve=] given |auctionConfig|.
@@ -3744,8 +4290,9 @@ Numeric value of a bid and the currency it is in.
Generated bid
-The output of running a Protected Audience `generateBid()` script, which needs to be scored by
-the seller.
+A bid that needs to be scored by the seller. The bid is either the output of running a Protected
+Audience `generateBid()` script, or an additional bid provided by the [:Ad-Auction-Additional-Bid:]
+response headers.
: bid
@@ -3756,11 +4303,15 @@ the seller.
original bid if the currency already matched, or a conversion provided by `scoreAd()`.
: ad
:: A [=string=]. JSON string to be passed to the scoring function.
+
+ Issue: TODO: Check whether [=generated bid/ad descriptor=] can be moved to
+ [=generated bid/bid ad=] to avoid duplication
+ (WICG/turtledove#868).
: ad descriptor
:: An [=ad descriptor=]. Render URL and size of the bid's ad.
: ad component descriptors
:: Null or a [=list=] of [=ad descriptors=]. Ad components associated with bid, if any. May have at
- most 20 URLs. Must be null if the interest group making this bid has a null
+ most 20 [=list/items=]. Must be null if the interest group making this bid has a null
[=interest group/ad components=] field.
: ad cost
:: Null or a {{double}}. Advertiser click or conversion cost passed from `generateBid()` to
@@ -3769,7 +4320,8 @@ the seller.
: modeling signals
:: Null or an {{unsigned short}}. A 0-4095 integer (12-bits) passed to `reportWin()`, with noising.
: interest group
-:: An [=interest group=], whose `generateBid()` invocation generated this bid.
+:: An [=interest group=], whose `generateBid()` invocation generated this bid, or specified by the
+ additional bid.
: bid ad
:: The [=interest group ad=] within [=generated bid/interest group=] to display.
: modified bid
@@ -3777,6 +4329,9 @@ the seller.
The bid value a component auction's `scoreAd()` script returns.
: bid duration
:: A [=duration=] in milliseconds. How long it took to run `generateBid()`.
+: provided as additional bid
+:: A [=boolean=], initially false.
+