From 60a244550030462da297dcba31a349605a6f1dfd Mon Sep 17 00:00:00 2001 From: Alex Turner Date: Tue, 25 Feb 2025 18:20:35 +0000 Subject: [PATCH 1/3] Spec: Private Aggregation error reporting Adds support for the new Private Aggregation error reporting feature to Shared Storage. See the related PAA spec change: https://github.com/patcg-individual-drafts/private-aggregation-api/pull/172 Also see the explainer: https://github.com/patcg-individual-drafts/private-aggregation-api/blob/main/error_reporting.md Slightly reorganizes the PAA integration with this spec for readability. --- spec.bs | 308 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 176 insertions(+), 132 deletions(-) diff --git a/spec.bs b/spec.bs index eff0b70..4fd6568 100644 --- a/spec.bs +++ b/spec.bs @@ -128,34 +128,6 @@ spec: attestation; urlPrefix: https://github.com/privacysandbox/attestation spec: private-aggregation-api; urlPrefix: https://patcg-individual-drafts.github.io/private-aggregation-api/ type: dfn text: Private Aggregation; url: - text: get the privateAggregation - text: determine if an origin is an aggregation coordinator - text: pre-specified report parameters - for: pre-specified report parameters - text: context ID - text: filtering ID max bytes - text: max contributions - text: batching scope - text: debug scope - text: process contributions for a batching scope - text: set the aggregation coordinator for a batching scope - text: determine if a report should be sent deterministically - text: mark a debug scope complete - text: set the pre-specified report parameters for a batching scope - text: aggregation coordinator - text: default filtering id max bytes - text: valid filtering id max bytes range - text: context id - text: scoping details - for: scoping details - text: get batching scope steps - text: get debug scope steps - text: private-aggregation - for: PrivateAggregation - text: allowed to use - text: scoping details; url: #privateaggregation-scoping-details - type: interface - text: PrivateAggregation spec: protected-audience; urlPrefix: https://wicg.github.io/turtledove/ type: dfn text: get storage interest groups for owner @@ -394,21 +366,27 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= : If it was fulfilled with value |index|: :: 1. If |index| is greater than |urlList|'s [=list/size=], then: 1. If |savedQueryName| is a [=string=] that is not the empty string, then run [=store the index for a saved query=] with |window|, |navigable|, |workletDataOrigin|, |moduleURLRecord|, |operationName|, |savedQueryName|, and the [=default selectURL index=]. - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}, and abort these steps. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. + 1. Run |privateAggregationCompletionTask| given [=Private Aggregation completion task trigger/normal completion=]. + 1. Return (|promise|, false). Note: The result index is beyond the input urls' size. This violates the selectURL() protocol, and we don't know which url should be selected. - Otherwise: + 1. Otherwise: 1. If |savedQueryName| is a [=string=] that is not the empty string, then run [=store the index for a saved query=] with |window|, |navigable|, |workletDataOrigin|, |moduleURLRecord|, |operationName|, |savedQueryName|, and |index|. 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=resolve=] |promise| with |index|. - 1. Run |privateAggregationCompletionTask|. + 1. Run |privateAggregationCompletionTask| given [=Private Aggregation completion task trigger/normal completion=]. : If it was rejected: :: 1. If |savedQueryName| is a [=string=] that is not the empty string, then run [=store the index for a saved query=] with |window|, |navigable|, |workletDataOrigin|, |moduleURLRecord|, |operationName|, |savedQueryName|, and the [=default selectURL index=]. 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. Note: This indicates that either |operationCtor|'s run() method encounters an error (where |operationCtor| is the parameter in {{SharedStorageWorkletGlobalScope/register()}}), or the result |index| is a non-integer value, which violates the selectURL() protocol, and we don't know which url should be selected. - 1. Run |privateAggregationCompletionTask|. + 1. If the promise was rejected as the operation was completed abruptly due to an uncaught exception: + + Issue: How to handle this properly. + 1. Run |privateAggregationCompletionTask| given [=Private Aggregation completion task trigger/uncaught exception=]. + 1. Otherwise, run |privateAggregationCompletionTask| given [=Private Aggregation completion task trigger/normal completion=]. 1. Return the [=tuple=] (|promise|, true). @@ -544,105 +522,6 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Run [=terminate a worklet global scope=] with [=this=]. -
- To obtain the aggregation coordinator given a - {{SharedStorageRunOperationMethodOptions}} |options|, perform the following - steps. They return an [=aggregation coordinator=], null or a {{DOMException}}: - - 1. If |options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"] - does not [=map/exist=], return null. - 1. If |options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"]["{{SharedStoragePrivateAggregationConfig/aggregationCoordinatorOrigin}}"] - does not [=map/exist=], return null. - 1. Return the result of [=obtaining the Private Aggregation coordinator=] - given - |options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"]["{{SharedStoragePrivateAggregationConfig/aggregationCoordinatorOrigin}}"]. -
- -
- To obtain the pre-specified report parameters given a - {{SharedStorageRunOperationMethodOptions}} |options| and a [=/browsing - context=] |context|, perform the following steps. They return a - [=pre-specified report parameters=], null, or a {{DOMException}}: - 1. If |options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"] - does not [=map/exist=], return null. - 1. Let |privateAggregationConfig| be - |options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"]. - 1. Let |contextId| be null. - 1. If |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/contextId}}"] - [=map/exists=], set |contextId| to - |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/contextId}}"]. - 1. If |contextId|'s [=string/length=] is greater than 64, return a new - {{DOMException}} with name "`DataError`". - 1. Let |filteringIdMaxBytes| be the [=default filtering ID max bytes=]. - 1. If |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/filteringIdMaxBytes}}"] - [=map/exists=], set |filteringIdMaxBytes| to - |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/filteringIdMaxBytes}}"]. - 1. If |filteringIdMaxBytes| is not [=set/contained=] in the [=valid filtering ID - max bytes range=], return a new {{DOMException}} with name "`DataError`". - 1. If |context|'s [=browsing context/fenced frame config instance=] is not null: - 1. If |filteringIdMaxBytes| is not the [=default filtering ID max bytes=] or - |contextId| is not null, return a new {{DOMException}} with name - "`DataError`". - 1. Let |maxContributions| be null. - 1. If - |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/maxContributions}}"] - [=map/exists=], set |maxContributions| to - |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/maxContributions}}"]. - 1. If |maxContributions| is zero, return a new {{DOMException}} with name - "`DataError`". - 1. Return a new [=pre-specified report parameters=] with the items: - : context ID - :: |contextId| - : [=pre-specified report parameters/filtering ID max bytes=] - :: |filteringIdMaxBytes| - : [=pre-specified report parameters/max contributions=] - :: |maxContributions| -
- -
- To set up the Private Aggregation scopes given an [=/origin=] - |workletDataOrigin|, a [=pre-specified report parameters=] or null - |preSpecifiedParams| and an [=aggregation coordinator=] or null - |aggregationCoordinator|, perform the following steps. They return an - algorithm. - - Note: The returned algorithm should be run when the associated operation is - complete. - - 1. Let |batchingScope| be a new [=batching scope=]. - 1. Let |debugScope| be a new [=debug scope=]. - 1. Let |privateAggregationTimeout| be null. - 1. Let |hasRunPrivateAggregationCompletionTask| be false. - 1. Let |privateAggregationCompletionTask| be an algorithm to perform the - following steps: - 1. If |hasRunPrivateAggregationCompletionTask|, return. - 1. Set |hasRunPrivateAggregationCompletionTask| to true. - 1. [=Mark a debug scope complete=] given |debugScope|. - 1. [=Process contributions for a batching scope=] given - |batchingScope|, |workletDataOrigin|, "shared-storage" - and |privateAggregationTimeout|. - 1. If |aggregationCoordinator| is not null, [=set the aggregation coordinator - for a batching scope=] given |aggregationCoordinator| and |batchingScope|. - 1. If |preSpecifiedParams| is not null: - 1. Let |isDeterministicReport| be the result of [=determining if a report - should be sent deterministically=] given |preSpecifiedParams|. - 1. If |isDeterministicReport|: - 1. Set |privateAggregationTimeout| to the [=/current wall time=] plus - the [=deterministic operation timeout duration=]. - 1. [=Set the pre-specified report parameters for a batching scope=] given - |preSpecifiedParams| and |batchingScope|. - 1. If |isDeterministicReport|, run the following steps [=in parallel=]: - 1. Wait until |privateAggregationTimeout|. - 1. Run |privateAggregationCompletionTask|. - 1. Return |privateAggregationCompletionTask|. -
- - The deterministic operation timeout duration is an - [=implementation-defined=] non-negative [=duration=] that controls how long a - Shared Storage operation may make Private Aggregation contributions if it is - triggering a deterministic report and, equivalently, when that report should - be sent after the operation begins. - ## Monkey Patch for [=Worklets=] ## {#worklet-monkey-patch} This specification will make some modifications to the [=Worklet=] standard to accommodate the needs of Shared Storage. @@ -809,6 +688,9 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= Note: Multiple operation invocations can be in-progress at the same time, each with a different batching scope and debug scope. However, only one can be currently executing. + 1. Set |privateAggregationObj|'s [=PrivateAggregation/should perform default + contributeToHistogramOnEvent() processing=] to the algorithm [=determine + whether to perform default contributeToHistogramOnEvent() processing=]. A trusted origin type is a [=string=] or [=list=] of [=strings=]. @@ -1209,6 +1091,169 @@ navigables section, add the following: + ## Private Aggregation integration ## {#private-aggregation-integration} + + A Private Aggregation completion task trigger is one of the following: +
+ : normal completion + :: The operation completed without any other triggers occurring. + : timeout + :: The operation ran for at least the [=deterministic operation timeout duration=] (without another trigger occurring). + : uncaught exception + :: An [=exception=] was [=exception/thrown=] without being caught. +
+ +
+ To obtain the aggregation coordinator given a + {{SharedStorageRunOperationMethodOptions}} |options|, perform the following + steps. They return an [=aggregation coordinator=], null or a {{DOMException}}: + + 1. If |options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"] + does not [=map/exist=], return null. + 1. If |options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"]["{{SharedStoragePrivateAggregationConfig/aggregationCoordinatorOrigin}}"] + does not [=map/exist=], return null. + 1. Return the result of [=obtaining the Private Aggregation coordinator=] + given + |options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"]["{{SharedStoragePrivateAggregationConfig/aggregationCoordinatorOrigin}}"]. +
+ +
+ To obtain the pre-specified report parameters given a + {{SharedStorageRunOperationMethodOptions}} |options| and a [=/browsing + context=] |context|, perform the following steps. They return a + [=pre-specified report parameters=], null, or a {{DOMException}}: + 1. If |options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"] + does not [=map/exist=], return null. + 1. Let |privateAggregationConfig| be + |options|["{{SharedStorageRunOperationMethodOptions/privateAggregationConfig}}"]. + 1. Let |contextId| be null. + 1. If |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/contextId}}"] + [=map/exists=], set |contextId| to + |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/contextId}}"]. + 1. If |contextId|'s [=string/length=] is greater than 64, return a new + {{DOMException}} with name "`DataError`". + 1. Let |filteringIdMaxBytes| be the [=default filtering ID max bytes=]. + 1. If |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/filteringIdMaxBytes}}"] + [=map/exists=], set |filteringIdMaxBytes| to + |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/filteringIdMaxBytes}}"]. + 1. If |filteringIdMaxBytes| is not [=set/contained=] in the [=valid filtering ID + max bytes range=], return a new {{DOMException}} with name "`DataError`". + 1. If |context|'s [=browsing context/fenced frame config instance=] is not null: + 1. If |filteringIdMaxBytes| is not the [=default filtering ID max bytes=] or + |contextId| is not null, return a new {{DOMException}} with name + "`DataError`". + 1. Let |maxContributions| be null. + 1. If + |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/maxContributions}}"] + [=map/exists=], set |maxContributions| to + |privateAggregationConfig|["{{SharedStoragePrivateAggregationConfig/maxContributions}}"]. + 1. If |maxContributions| is zero, return a new {{DOMException}} with name + "`DataError`". + 1. Return a new [=pre-specified report parameters=] with the items: + : context ID + :: |contextId| + : [=pre-specified report parameters/filtering ID max bytes=] + :: |filteringIdMaxBytes| + : [=pre-specified report parameters/max contributions=] + :: |maxContributions| +
+ +
+ To set up the Private Aggregation scopes given an [=/origin=] + |workletDataOrigin|, a [=pre-specified report parameters=] or null + |preSpecifiedParams| and an [=aggregation coordinator=] or null + |aggregationCoordinator|, perform the following steps. They return an + algorithm that takes a [=Private Aggregation completion task trigger=] + argument. + + Note: The returned algorithm should be run when the associated operation is + complete. + + 1. Let |batchingScope| be a new [=batching scope=]. + 1. Let |debugScope| be a new [=debug scope=]. + 1. Let |privateAggregationTimeout| be null. + 1. Let |hasRunPrivateAggregationCompletionTask| be false. + 1. Let |privateAggregationCompletionTask| be an algorithm that is given a + [=Private Aggregation completion task trigger=] |trigger| that performs + the following steps: + 1. If |hasRunPrivateAggregationCompletionTask|, return. + 1. Set |hasRunPrivateAggregationCompletionTask| to true. + 1. [=Mark a debug scope complete=] given |debugScope|. + 1. If |trigger| is [=Private Aggregation completion task trigger/ + timeout=], set |privateAggregationTimeout| to null. + + Note: A null timeout is used to identify that a timeout caused this + task to be run. + 1. If |trigger| is [=Private Aggregation completion task trigger/ + uncaught exception=]: + 1. If [=uncaught exception contribution cache=][|batchingScope|] + [=map/exists=]: + 1. [=list/For each=] |entry| of [=uncaught exception + contribution cache=][|batchingScope|], [=append an entry to + the contribution cache|append=] |entry| to the + contribution cache. + 1. [=map/Remove=] [=uncaught exception contribution + cache=][|batchingScope|]. + 1. [=Process contributions for a batching scope=] given + |batchingScope|, |workletDataOrigin|, "shared-storage" + and |privateAggregationTimeout|. + 1. If |aggregationCoordinator| is not null, [=set the aggregation coordinator + for a batching scope=] given |aggregationCoordinator| and |batchingScope|. + 1. If |preSpecifiedParams| is not null: + 1. Let |isDeterministicReport| be the result of [=determining if a report + should be sent deterministically=] given |preSpecifiedParams|. + 1. If |isDeterministicReport|: + 1. Set |privateAggregationTimeout| to the [=/current wall time=] plus + the [=deterministic operation timeout duration=]. + 1. [=Set the pre-specified report parameters for a batching scope=] given + |preSpecifiedParams| and |batchingScope|. + 1. If |isDeterministicReport|, run the following steps [=in parallel=]: + 1. Wait until |privateAggregationTimeout|. + 1. Run |privateAggregationCompletionTask| with [=Private Aggregation + completion task trigger/timeout=] bound as the argument. + 1. Return |privateAggregationCompletionTask|. +
+ + The deterministic operation timeout duration is an + [=implementation-defined=] non-negative [=duration=] that controls how long a + Shared Storage operation may make Private Aggregation contributions if it is + triggering a deterministic report and, equivalently, when that report should + be sent after the operation begins. + +
+ To determine whether to perform default contributeToHistogramOnEvent() + processing given a {{PrivateAggregation}} |this|, a {{DOMString}} |event| + and a [=map=] (with {{DOMString}} keys) |contribution|, perform the following + steps. They return a boolean or an [=exception=]. + 1. If |event| is not "reserved.uncaught-exception", return true. + + Note: Default processing is only overridden for the custom event. + 1. Set |contribution| to the result of [=converted to an IDL value|converting=] + |contribution|'s [=converted to a JavaScript value|JavaScript value=] to the + IDL type {{PAHistogramContribution}}. + + Note: This throws a {{TypeError}} if |contribution| is not compatible. + 1. Let |maybeContributionCacheEntry| be the result of [=validating a histogram + contribution=] given |contribution| and |this|'s [=PrivateAggregation/ + scoping details=]. + 1. If |maybeContributionCacheEntry| is an [=exception=], return + |maybeContributionCacheEntry|. + 1. [=Assert=]: |maybeContributionCacheEntry| is a [=contribution cache entry=]. + 1. Let |maybeContributionCacheEntry|'s [=contribution cache entry/error + event=] be [=already triggered external error=]. + 1. Let |batchingScope| be |maybeContributionCacheEntry|'s [=contribution + cache entry/batching scope=]. + 1. If [=uncaught exception contribution cache=][|batchingScope|] does not + [=map/exist=], [=map/set=] [=uncaught exception contribution + cache=][|batchingScope|] to a new [=list=]. + 1. [=list/Append=] |maybeContributionCacheEntry| to [=uncaught exception + contribution cache=][|batchingScope|]. + +
+ +The [=user agent=] holds a uncaught exception contribution cache, +which is a [=map=] from [=batching scopes=] to [=contribution cache entries=]. + Shared Storage's Backend {#backend} =================================== The Shared Storage API will integrate into the [=Storage Model|Storage API=] as below, via [=storage endpoint/registering=] a new [=storage endpoint=]. @@ -2436,7 +2481,6 @@ The [=obtain a lock manager=] algorithm should be prepended with the following s Note: With the default {{LockOptions}}, |callback| will eventually be called when the lock is granted (i.e., the lock request won't fail). - Permissions Policy Integration {#permission} ============================================ From 257ae0f699ebe5b33ca4327f2ed1ea657ae85968 Mon Sep 17 00:00:00 2001 From: Alex Turner Date: Wed, 12 Mar 2025 22:48:19 +0000 Subject: [PATCH 2/3] any rejected promise -> uncaught exception --- spec.bs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/spec.bs b/spec.bs index 4fd6568..99e5ffc 100644 --- a/spec.bs +++ b/spec.bs @@ -382,11 +382,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. Note: This indicates that either |operationCtor|'s run() method encounters an error (where |operationCtor| is the parameter in {{SharedStorageWorkletGlobalScope/register()}}), or the result |index| is a non-integer value, which violates the selectURL() protocol, and we don't know which url should be selected. - 1. If the promise was rejected as the operation was completed abruptly due to an uncaught exception: - - Issue: How to handle this properly. - 1. Run |privateAggregationCompletionTask| given [=Private Aggregation completion task trigger/uncaught exception=]. - 1. Otherwise, run |privateAggregationCompletionTask| given [=Private Aggregation completion task trigger/normal completion=]. + 1. Run |privateAggregationCompletionTask| given [=Private Aggregation completion task trigger/uncaught exception=]. 1. Return the [=tuple=] (|promise|, true). From 80e2d7caf315bc761feea00f34dc405e9fe24b81 Mon Sep 17 00:00:00 2001 From: Alex Turner Date: Wed, 19 Mar 2025 01:39:24 +0000 Subject: [PATCH 3/3] bikeshed fix --- spec.bs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec.bs b/spec.bs index 99e5ffc..ae6b7c5 100644 --- a/spec.bs +++ b/spec.bs @@ -673,11 +673,11 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Set |privateAggregationObj|'s [=PrivateAggregation/scoping details=] to a new [=/scoping details=] with the items: : [=scoping details/get batching scope steps=] - :: An algorithm that returns the [=batching scope=] that is scheduled to + :: An algorithm that returns the [=/batching scope=] that is scheduled to be passed to [=process contributions for a batching scope=] when the call currently executing in |scope| returns. : [=scoping details/get debug scope steps=] - :: An algorithm that returns the [=debug scope=] that is scheduled to be + :: An algorithm that returns the [=/debug scope=] that is scheduled to be passed to [=mark a debug scope complete=] when the call currently executing in |scope| returns. @@ -1165,8 +1165,8 @@ navigables section, add the following: Note: The returned algorithm should be run when the associated operation is complete. - 1. Let |batchingScope| be a new [=batching scope=]. - 1. Let |debugScope| be a new [=debug scope=]. + 1. Let |batchingScope| be a new [=/batching scope=]. + 1. Let |debugScope| be a new [=/debug scope=]. 1. Let |privateAggregationTimeout| be null. 1. Let |hasRunPrivateAggregationCompletionTask| be false. 1. Let |privateAggregationCompletionTask| be an algorithm that is given a @@ -1248,7 +1248,7 @@ navigables section, add the following: The [=user agent=] holds a uncaught exception contribution cache, -which is a [=map=] from [=batching scopes=] to [=contribution cache entries=]. +which is a [=map=] from [=/batching scopes=] to [=contribution cache entries=]. Shared Storage's Backend {#backend} ===================================