From 9418e4d8e886d5e2e2a197cb9583f62b90ee12be Mon Sep 17 00:00:00 2001 From: Martin Thomson Date: Fri, 13 Sep 2024 17:36:32 +1000 Subject: [PATCH 1/3] Sketch in last-touch attribution --- api.bs | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 5 deletions(-) diff --git a/api.bs b/api.bs index 7dab622..e389d54 100644 --- a/api.bs +++ b/api.bs @@ -353,7 +353,11 @@ navigator.privateAttribution.measureConversion({ // the number of buckets in the histogram histogramSize: 20, + // the amount of privacy budget to use + epsilon: 1, + // the attribution logic to use + logic: "last-touch", // the value to assign to the histogram index of the impression value: 3, @@ -371,16 +375,16 @@ navigator.privateAttribution.measureConversion({ dictionary PrivateAttributionConversionOptions { required DOMString aggregator; + double epsilon = 1.0; required unsigned long histogramSize; - double epsilon = 1.0; PrivateAttributionLogic logic = "last-touch"; unsigned long value = 1; unsigned long lookbackDays = Infinity; sequence<DOMString> ads = []; - sequence<DOMString> sources = []; + sequence<DOMString> impressionSites = []; }; [SecureContext, Exposed=Window] @@ -403,7 +407,7 @@ The arguments to <a method for=PrivateAttribution>measureConversion()</a> are as <dd>The conversion value</dd> <dt><dfn>lookbackDays</dfn></dt> <dt><dfn>ads</dfn></dt> - <dt><dfn>sources</dfn></dt> + <dt><dfn>impressionSites</dfn></dt> </dl> @@ -450,7 +454,7 @@ how to handle weeks in which the [=privacy budget=] is insufficient, and (optionally) how to process any additional parameters that might be used. -### Last Touch Attribution ## {#logic-last-touch} +### Last Touch Attribution ### {#logic-last-touch} The <dfn enum-value for=PrivateAttributionLogic>last-touch</dfn> [=attribution logic=] indicates that the browser should select @@ -458,12 +462,90 @@ the last impression that matches the [[#logic-matching|common matching logic]]. The entire [=conversion value=] is allocated to the histogram bucket that was saved with the impression. +Last touch attribution does not select any impression +that was saved during a week +that does not have sufficient [=privacy budget=]. +If impressions match from a week +that does not have enough [=privacy budget=], +impressions are not matched for any preceding weeks. +That is, once a week has a matching impression +and insufficient budget, +the process will set a value of zero for all histogram buckets. + +To <dfn ignore>fill a histogram using last-touch attribution</dfn>, +given |options|: + +1. Initialize |impression| to a null value. + +1. Initialize |value| to |options|.|value|. + +1. Let |now| be the current time.<!-- TODO: cite HRTIME spec --> + +1. For each |week| starting from the current week + to the oldest week supported by the [=user agent=]: + + 1. Let |impressions| be the result of invoking [=common matching logic=] + with |options|, |week|, and |now|. + + 1. If |impressions| is not empty: + + 1. Retain the value of |week|. + + 1. Set |impression| to the value in |impressions| + with the most recent |impression|.timestamp. + <!-- TODO define a type for stored impressions --> + + 1. Exit the loop. + +1. If |impression| is null, let |budgetOk| be false. + +1. Otherwise, let |budgetOk| be the result of [=deduct privacy budget=] + with |week| and |options|.{{PrivateAttributionConversionOptions/epsilon}}. + +1. If |budgetOk| is false, set |value| to 0. -### Common Matching Logic ### {#logic-matching} +1. If |impression|.|histogramIndex| + is |options|.{{PrivateAttributionConversionOptions/histogramSize}} or greater, + set |value| to 0. + +1. If |value| is not 0, set |index| + to |impression|.{{PrivateAttributionImpressionOptions/histogramIndex}}. + +1. Otherwise, set |index| to 0. + +1. Return a histogram containing |options|.{{PrivateAttributionConversionOptions/histogramSize}} values, + with a value of |value| at an index of |index| + and a value of zero at all other indices. + + +### Common Impression Matching Logic ### {#logic-matching} TODO specify how to match using "lookbackDays", "ads" and "sources". +To perform <dfn>common matching logic</dfn>, +given |options|, |week|, and [=moment=] |now|: + +1. If number of days since the end of |week| exceeds |lookbackDays|, + return an empty set. + +1. Initialize |matching| to an empty set. +1. For each |impression| in the saved impressions for the |week|: + + 1. If |now| - |lookbackDays| is after |impression|.timestamp, + continue the loop. + + 1. If |options|.{{PrivateAttributionConversionOptions/ads}} + does not contain |impression|.ad, + continue the loop. + + 1. If |options|.{{PrivateAttributionConversionOptions/impressionSites}} + does not contain |impression|.source, + continue the loop. + + 1. Add |impression| to |matching|. + +1. Return |matching|. ## User control and visibility ## {#user-control} @@ -774,6 +856,10 @@ whether their request for a [=conversion report=] has caused a safety limit to be exceeded. +### Privacy Budget Deduction ### {#dp-deduction} + +To <dfn>deduct privacy budget</dfn>, do this... + ## Differential Privacy Mechanisms ## {#dp-mechanism} From c8139ecf83855afa05e79fbf328209091e71f628 Mon Sep 17 00:00:00 2001 From: Andy Leiserson <aleiserson@mozilla.com> Date: Mon, 16 Sep 2024 17:50:01 -0700 Subject: [PATCH 2/3] More API updates --- api.bs | 114 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 77 insertions(+), 37 deletions(-) diff --git a/api.bs b/api.bs index 5f6477a..ee02d7d 100644 --- a/api.bs +++ b/api.bs @@ -353,27 +353,43 @@ Upon receiving a set of encrypted histograms from a site, the aggregation servic # API Details # {#api} -Before using the other Private Attribution APIs, a site must -[[#list-aggregation-services-api|list aggregation services]] to discover the aggregation services -that are supported. -The page may select any of the supported services returned by -<a method for=PrivateAttribution>listAggregationServices()</a>. +A site using the Private Attribution API will typically register either +[=impressions=] or [=conversions=], but in some cases the same site may +do both. + +To register an impression, a site calls +<a method for=PrivateAttribution>saveImpression()</a>. No preparation is +required to use this API beyond collecting parameter values, although +it may be useful to examine the supported +<a attribute for=PrivateAttribution>aggregationServices</a> in deciding +whether to use the Private Attribution API. + +To request a conversion report, a site calls +<a method for=PrivateAttribution>measureConversion()</a>. +Before calling this API, a site must +select a supported [=aggregation service=]. +The page may select any of the supported services found in +<a attribute for=PrivateAttribution>aggregationServices</a>. The name of the selected service must be supplied as the `aggregator` member of the {{PrivateAttributionConversionOptions}} dictionary when calling the <a method for=PrivateAttribution>measureConversion()</a> method. -## Finding a Supported Aggregation Service ## {#list-aggregation-services-api} +## Finding a Supported Aggregation Service ## {#find-aggregation-service} <p class=issue>Is any additional information required in the {{PrivateAttributionAggregationService}} dictionary? Do we want to rename `apiVersion` to `protocol`? And we should definitely define an enum for it. -The <dfn method for=PrivateAttribution>listAggregationServices()</dfn> method -returns a list of aggregation services supported by the [=user agent=]. The page +The <dfn attribute for=PrivateAttribution>aggregationServices</dfn> attribute +contains a list of aggregation services supported by the [=user agent=]. The page must select and specify one of these services when calling the <a method for=PrivateAttribution>measureConversion()</a> method. +It may also be useful to query the supported services +before registering an impression, +but that is not required, +and impressions are not scoped to a single aggregation service. <xmp class=idl> dictionary PrivateAttributionAggregationService { @@ -387,7 +403,8 @@ interface PrivateAttribution { }; -The arguments to listAggregationServices() are as follows: +The aggregationServices attribute +contains the following information about each supported aggregation service:
name
@@ -407,14 +424,14 @@ The arguments to listAggregationServices() ## Saving Impressions ## {#save-impression-api} -The saveImpression() method requests +The saveImpression() method requests that the [=user agent=] record an [=impression=] in the [=impression store=].
 navigator.privateAttribution.saveImpression({
   histogramIndex: 3,
-  ad: "sample-campaign-eijb",       // a unique identifier for the ad placement
-  conversionSite: "advertiser.example",     // the advertiser site where a conversion will occur
+  ad: "sample-campaign-eijb",
+  conversionSite: "advertiser.example",
 });
 
@@ -441,6 +458,20 @@ The arguments to saveImpression() are as fo [=impression=] with a subsequent [=conversion=], the [=conversion value=] will be added to the histogram bucket identified by this index. +
ad
+
+ A unique ad identifier for the impression. The + ad identifier is used to identify which impressions may receive attribution + from a [=conversion=]. The Private Attribution API does not impose a particular + format on ad identifiers. If an implementation imposes a maximum ad identifer + length, it must be at least 64 code points. +
+
conversionSite
+
+ The site where [=conversions=] for this impression may occur, identified by + its domain name. The measureConversion() + method will only attribute to this impression when called by the indicated + site.
lifetimeDays
A "time to live" (in days) after which the [=impression=] can no longer @@ -452,11 +483,12 @@ The arguments to saveImpression() are as fo ### Operation ### {#save-impression-api-operation} -1. Validate the page-supplied API inputs -2. Collect the implict API inputs: +1. Collect the implicit API inputs: 1. The current timestamp 2. The impression site domain -3. If the private attribution API is enabled, save the impression to the store. +1. Validate the page-supplied API inputs +1. If the private attribution API is enabled, save the impression to the + [=impression store=]. ## Requesting Attribution for a Conversion ## {#measure-conversion} @@ -467,7 +499,10 @@ and return a [=conversion report=]. The measureConversion() method always returns a conversion report, -regardless of whether matching [=impression|impression(s)=] were found. +regardless of whether matching [=impression|impression(s)=] are found. +If there is no match, or if [[#dp|differential privacy]] disallows +reporting the attribution, the returned conversion report will not +contribute to the histogram, i.e., will be uniformly zero.
 navigator.privateAttribution.measureConversion({
@@ -519,14 +554,24 @@ The arguments to measureConversion() are as
 
aggregator
- A selection from the [=aggregation services=] that can be listed using listAggregationServices(). + A selection from the [=aggregation services=] that can be found in aggregationServices.
-
histogramSize
epsilon
+
The amount of [=privacy budget=] to expend on this [=conversion report=].
+
histogramSize
+
The number of histogram buckets to use in the [=conversion report=].
logic
+
+ A selection from PrivateAttributionLogic indicating the + [=attribution logic=] to use. +
value
-
The [=conversion value=]
+
+ The [=conversion value=]. If an attribution is made and [[#dp|privacy]] + restrictions are satisfied, this value will be encoded into the [=conversion + report=]. +
lookbackDays
An integer number of days. Only impressions occurring within the past `lookbackDays` may match this [=conversion=].
ads
@@ -538,21 +583,18 @@ The arguments to measureConversion() are as ### Operation ### {#measure-conversion-api-operation} -1. Validate the page-supplied API inputs -2. Collect the implicit API inputs +1. Collect the implicit API inputs 1. The current timestamp 2. The conversion site domain -3. Set |reportedConversionValue| to 0. -4. If the private attribution API is enabled, search for a matching impression using the [[#logic-matching|common matching logic]]. -5. If a matching impression was found: - 1. Set |histogramIndex| to the value from the matching impression - 2. Set |reportedConversionValue| to the smaller of the following: - 1. The conversion value passed to the MeasureConversion API. - 2. The limit on conversion value determined by the remaining privacy budget. -6. Update the privacy budget store to reflect the reported conversion value. -7. Construct a report from |reportedConversionValue|, |histogramIndex|, and histogramSize. -8. Encrypt the report. -9. Return the encrypted report. +1. Validate the page-supplied API inputs + 1. If logic + is specified, and the value is anything other than + "last-touch", + return an error. +1. If the private attribution API is enabled, + invoke the routine to [=fill a histogram using last-touch attribution=]. +1. Encrypt the report. +1. Return the encrypted report. ## Impression store ## {#s-impression-store} @@ -627,7 +669,7 @@ That is, once a week has a matching impression and insufficient budget, the process will set a value of zero for all histogram buckets. -To fill a histogram using last-touch attribution, +To fill a histogram using last-touch attribution, given |options|: 1. Initialize |impression| to a null value. @@ -659,7 +701,7 @@ given |options|: 1. If |budgetOk| is false, set |value| to 0. -1. If |impression|.|histogramIndex| +1. If |impression|.histogramIndex is |options|.{{PrivateAttributionConversionOptions/histogramSize}} or greater, set |value| to 0. @@ -679,8 +721,6 @@ TODO specify how to match using "lookbackDays", "ads" and "impressionSites". Discuss "infinite" lookbackDays. Clarify when it apples. When field is missing? Zero? -ad identifier - To perform common matching logic, given |options|, |week|, and [=moment=] |now|: From 9fb2b158db1a86df0fc76624eb3f5bba2c633f4c Mon Sep 17 00:00:00 2001 From: Martin Thomson Date: Tue, 24 Sep 2024 07:08:40 +1000 Subject: [PATCH 3/3] Small fixes of names Co-authored-by: Andy Leiserson --- api.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api.bs b/api.bs index ee02d7d..ff8ef0a 100644 --- a/api.bs +++ b/api.bs @@ -670,11 +670,11 @@ and insufficient budget, the process will set a value of zero for all histogram buckets. To fill a histogram using last-touch attribution, -given |options|: +given |options|: 1. Initialize |impression| to a null value. -1. Initialize |value| to |options|.|value|. +1. Initialize |value| to |options|.{{PrivateAttributionConversionOptions/value}}. 1. Let |now| be the current time. @@ -739,7 +739,7 @@ given |options|, |week|, and [=moment=] |now|: continue the loop. 1. If |options|.{{PrivateAttributionConversionOptions/impressionSites}} - does not contain |impression|.source, + does not contain |impression|.impressionSite, continue the loop. 1. Add |impression| to |matching|.