From 05d7a244fd776d54e63f8e5ad6d7c559ea56400c Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Mon, 22 Jan 2024 21:31:25 +0000 Subject: [PATCH 01/21] Filter Aggregatable values --- index.bs | 86 +++++++++++++++++++----- ts/src/header-validator/validate-json.ts | 5 +- 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/index.bs b/index.bs index 9f81d48627..2194805f16 100644 --- a/index.bs +++ b/index.bs @@ -797,6 +797,18 @@ An aggregatable trigger data is a [=struct=] with the following items: +

Aggregatable value

+ +An aggregatable value is a [=struct=] with the following items: + +
+: values +:: An [=ordered map=] whose [=map/key|keys=] are [=strings=] and whose [=map/value|values=] are non-negative 32-bit integers. +: filters +:: A [=list=] of [=filter configs=]. +: negated filters +:: A [=list=] of [=filter configs=]. +

Aggregatable dedup key

An aggregatable dedup key is a [=struct=] with the following items: @@ -869,8 +881,7 @@ An attribution trigger is a [=struct=] with the following items: : aggregatable trigger data :: A [=list=] of [=aggregatable trigger data=]. : aggregatable values -:: An [=ordered map=] whose [=map/key|keys=] are [=strings=] and whose - [=map/value|values=] are non-negative 32-bit integers. +:: A [=list=] of [=aggregatable value=]. : aggregatable dedup keys :: A [=list=] of [=aggregatable dedup key=]. : verifications @@ -2399,18 +2410,50 @@ To parse aggregatable trigger data given an [=ordered map=] |map|: 1. [=list/Append=] |aggregatableTrigger| to |aggregatableTriggerData|. 1. Return |aggregatableTriggerData|. +To parse aggregatable key-values given an [=ordered map=] |map|: + +1. [=map/iterate|For each=] |key| → |value| of |values|: + 1. If |key|'s [=string/length=] is greater than the [=max length per aggregation key identifier=], + return null. + 1. If |value| is not an integer, return null. + 1. If |value| is less than or equal to 0, return null. + 1. If |value| is greater than [=allowed aggregatable budget per source=], return null. +1. Return |values|. + To parse aggregatable values given an [=ordered map=] |map|: 1. If |map|["`aggregatable_values`"] does not [=map/exist=], return «[]». 1. Let |values| be |map|["`aggregatable_values`"]. -1. If |values| is not an [=ordered map=], return null. -1. [=map/iterate|For each=] |key| → |value| of |values|: - 1. If |key|'s [=string/length=] is greater than the [=max length per aggregation key identifier=], - return null. - 1. If |value| is not an integer, return null. - 1. If |value| is less than or equal to 0, return null. - 1. If |value| is greater than [=allowed aggregatable budget per source=], return null. -1. Return |values|. +1. If |values| is not a [=ordered map=] or a [=list=], return null. +1. Let |aggregatableValues| be a [=list=] of [=aggregatable value=], initially empty. +1. If |values| is an [=ordered map=]: + 1. Let |aggregatableKeyValues| be the result of running [=parse aggregatable key-values=] with |values|. + 1. Let |aggregatableValue| be a new [=aggregatable value=] with the items: + : [=aggregatable value/values=] + :: |aggregatableKeyValues| + : [=aggregatable value/filters=] + :: «[]» + : [=aggregatable value/negated filters=] + :: «[]» + 1. [=list/Append=] |aggregatableValue| to |aggregatableValues|. + 1. Return |aggregatableValues| +1. [=list/iterate|For each=] |value| of |values|: + 1. If |value| is not an [=ordered map=], return null. + 1. If |value|["`values`"] does not [=map/exist=], return null. + 1. Let |aggregatableKeyValues| be the result of running [=parse aggregatable key-values=] with |value|["`values`"]. + 1. If |aggregatableKeyValues| is null, return null. + 1. Let |filterPair| be the result of running [=parse a filter pair=] with + |aggregatableKeyValues|. + 1. If |filterPair| is null, return null. + 1. Let |aggregatableValue| be a new [=aggregatable value=] with the items: + : [=aggregatable value/values=] + :: |aggregatableKeyValues| + : [=aggregatable value/filters=] + :: |filterPair|[0] + : [=aggregatable value/negated filters=] + :: |filterPair|[1] + 1. [=list/Append=] |aggregatableValue| to |aggregatableValues|. +1. Return |aggregatableValues|. To parse aggregatable dedup keys given an [=ordered map=] |map|: @@ -2665,14 +2708,21 @@ To create [=aggregatable contributions=] given an [=attribution sourc [=aggregatable trigger data/key piece=]. 1. Let |aggregatableValues| be |trigger|'s [=attribution trigger/aggregatable values=]. 1. Let |contributions| be a new empty [=list=]. -1. [=map/iterate|For each=] |id| → |key| of |aggregationKeys|: - 1. If |aggregatableValues|[|id|] does not [=map/exist=], [=iteration/continue=]. - 1. Let |contribution| be a new [=aggregatable contribution=] with the items: - : [=aggregatable contribution/key=] - :: |key| - : [=aggregatable contribution/value=] - :: |aggregatableValues|[|id|] - 1. [=list/Append=] |contribution| to |contributions|. +1. [=list/iterate|For each=] |aggregatableValue| of |aggregatableValues|: + 1. [=map/iterate|For each=] |id| → |key| of |aggregationKeys|: + 1. If |aggregatableValue|'s [=aggregatable value/values=][|id|] does not [=map/exist=] or if + the result of running [=match an attribution source's filter data against filters and negated filters=] with + |source|, |aggregatableValue|'s [=aggregatable value/filters=], + |triggerData|'s [=aggregatable value/negated filters=], and + |trigger|'s [=attribution trigger/trigger time=] + is false, [=iteration/continue=]:. + 1. Let |contribution| be a new [=aggregatable contribution=] with the items: + : [=aggregatable contribution/key=] + :: |key| + : [=aggregatable contribution/value=] + :: |aggregatableValues|[|id|] + 1. [=list/Append=] |contribution| to |contributions|. + 1. If |contributions| is not [=list/is empty|empty=], [=iteration/Break=]. 1. Return |contributions|.

Can source create aggregatable contributions

diff --git a/ts/src/header-validator/validate-json.ts b/ts/src/header-validator/validate-json.ts index 0c9d5f9fc8..9a532ebea3 100644 --- a/ts/src/header-validator/validate-json.ts +++ b/ts/src/header-validator/validate-json.ts @@ -1209,6 +1209,9 @@ function aggregatableKeyValue( } function aggregatableValues(ctx: Context, j: Json): Maybe> { + return typeSwitch(ctx, j, { + object: (ctx, j) => keyValues(ctx, j, aggregatableKeyValue) + list: (ctx, j) => set(ctx, j, suitableSite, { minLength: 1, maxLength: 3 }), return keyValues(ctx, j, aggregatableKeyValue) } @@ -1350,7 +1353,7 @@ export type Trigger = CommonDebug & aggregatableDedupKeys: AggregatableDedupKey[] aggregatableTriggerData: AggregatableTriggerDatum[] aggregatableSourceRegistrationTime: AggregatableSourceRegistrationTime - aggregatableValues: Map + aggregatableValues: Map | Map[] aggregationCoordinatorOrigin: string | null eventTriggerData: EventTriggerDatum[] triggerContextID: string | null From 63394703e97b47fe1e068176404b6256b759c652 Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Tue, 23 Jan 2024 15:17:58 +0000 Subject: [PATCH 02/21] Spec change first draft --- index.bs | 8 +++++--- ts/src/header-validator/validate-json.ts | 5 +---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index 2194805f16..951a4edf97 100644 --- a/index.bs +++ b/index.bs @@ -809,6 +809,8 @@ An aggregatable value is a [=struct=] with the following items: : negated filters :: A [=list=] of [=filter configs=]. +
+

Aggregatable dedup key

An aggregatable dedup key is a [=struct=] with the following items: @@ -2424,7 +2426,7 @@ To parse aggregatable values given an [=ordered map=] |map|: 1. If |map|["`aggregatable_values`"] does not [=map/exist=], return «[]». 1. Let |values| be |map|["`aggregatable_values`"]. -1. If |values| is not a [=ordered map=] or a [=list=], return null. +1. If |values| is not an [=ordered map=] or a [=list=], return null. 1. Let |aggregatableValues| be a [=list=] of [=aggregatable value=], initially empty. 1. If |values| is an [=ordered map=]: 1. Let |aggregatableKeyValues| be the result of running [=parse aggregatable key-values=] with |values|. @@ -2713,9 +2715,9 @@ To create [=aggregatable contributions=] given an [=attribution sourc 1. If |aggregatableValue|'s [=aggregatable value/values=][|id|] does not [=map/exist=] or if the result of running [=match an attribution source's filter data against filters and negated filters=] with |source|, |aggregatableValue|'s [=aggregatable value/filters=], - |triggerData|'s [=aggregatable value/negated filters=], and + |aggregatableValue|'s [=aggregatable value/negated filters=], and |trigger|'s [=attribution trigger/trigger time=] - is false, [=iteration/continue=]:. + is false, [=iteration/continue=]. 1. Let |contribution| be a new [=aggregatable contribution=] with the items: : [=aggregatable contribution/key=] :: |key| diff --git a/ts/src/header-validator/validate-json.ts b/ts/src/header-validator/validate-json.ts index 9a532ebea3..0c9d5f9fc8 100644 --- a/ts/src/header-validator/validate-json.ts +++ b/ts/src/header-validator/validate-json.ts @@ -1209,9 +1209,6 @@ function aggregatableKeyValue( } function aggregatableValues(ctx: Context, j: Json): Maybe> { - return typeSwitch(ctx, j, { - object: (ctx, j) => keyValues(ctx, j, aggregatableKeyValue) - list: (ctx, j) => set(ctx, j, suitableSite, { minLength: 1, maxLength: 3 }), return keyValues(ctx, j, aggregatableKeyValue) } @@ -1353,7 +1350,7 @@ export type Trigger = CommonDebug & aggregatableDedupKeys: AggregatableDedupKey[] aggregatableTriggerData: AggregatableTriggerDatum[] aggregatableSourceRegistrationTime: AggregatableSourceRegistrationTime - aggregatableValues: Map | Map[] + aggregatableValues: Map aggregationCoordinatorOrigin: string | null eventTriggerData: EventTriggerDatum[] triggerContextID: string | null From 80db5cd13b347a17835d0e8a5727652c10a93866 Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Tue, 23 Jan 2024 22:33:45 +0000 Subject: [PATCH 03/21] adds tests --- ts/src/header-validator/trigger.test.ts | 63 +++++++++++++++- ts/src/header-validator/validate-json.ts | 91 ++++++++++++++---------- 2 files changed, 114 insertions(+), 40 deletions(-) diff --git a/ts/src/header-validator/trigger.test.ts b/ts/src/header-validator/trigger.test.ts index f8e950d0ba..292f2a602b 100644 --- a/ts/src/header-validator/trigger.test.ts +++ b/ts/src/header-validator/trigger.test.ts @@ -81,7 +81,13 @@ const testCases: jsontest.TestCase[] = [ sourceKeys: new Set(['x']), }, ], - aggregatableValues: new Map([['x', 5]]), + aggregatableValues: [ + { + aggregatableValue: new Map([['x', 5]]), + positive: [], + negative: [], + } + ], debugKey: 5n, debugReporting: true, eventTriggerData: [ @@ -118,6 +124,27 @@ const testCases: jsontest.TestCase[] = [ ], }), }, + { + name: 'aggregatable-values-list-with-filters', + json: `{ + "aggregatable_values": [ + { + "values": { + "a": 1 + }, + "filters": [{"g": []}, {"h": []}], + "not_filters": [{"g": []}, {"h": []}] + }, + { + "values": { + "b": 2 + }, + "filters": [{"i": []}, {"j": []}], + "not_filters": [{"i": []}, {"j": []}] + } + ] + }`, + }, { name: 'or-filters', json: `{ @@ -320,7 +347,7 @@ const testCases: jsontest.TestCase[] = [ expectedErrors: [ { path: ['aggregatable_values'], - msg: 'must be an object', + msg: 'must be a list or object', }, ], }, @@ -364,6 +391,38 @@ const testCases: jsontest.TestCase[] = [ }, ], }, + { + name: 'aggregatable-values-list-values-field-missing', + json: `{ + "aggregatable_values": [ + { + "a": 1 + } + ] + }`, + expectedErrors: [ + { + path: ['aggregatable_values',0,'values'], + msg: 'aggregatable values in a list must be defined in "values" field.', + }, + ], + }, + { + name: 'aggregatable-values-list-wrong-type', + json: `{ + "aggregatable_values": [ + { + "values": [] + } + ] + }`, + expectedErrors: [ + { + path: ['aggregatable_values',0,'values'], + msg: 'must be an object', + }, + ], + }, { name: 'inconsistent-aggregatable-keys', diff --git a/ts/src/header-validator/validate-json.ts b/ts/src/header-validator/validate-json.ts index 26c7776408..51114398eb 100644 --- a/ts/src/header-validator/validate-json.ts +++ b/ts/src/header-validator/validate-json.ts @@ -61,6 +61,8 @@ function struct( if (warnUnknown) { for (const key in d) { + console.log("timestamp: " + Date.now() +" - unknown") + console.log(d) ctx.scope(key, () => ctx.warning('unknown field')) } } @@ -80,6 +82,7 @@ function field( ctx.scope(name, () => { const v = d[name] if (v === undefined) { + console.log(name + " not found"); if (valueIfAbsent === undefined) { ctx.error('required') return None @@ -1194,6 +1197,10 @@ function aggregatableTriggerData( ) } +export type AggregatableValue = FilterPair & { + aggregatableValue: Map +} + function aggregatableKeyValue( ctx: Context, [key, j]: [string, Json] @@ -1208,8 +1215,16 @@ function aggregatableKeyValue( ) } -function aggregatableValues(ctx: Context, j: Json): Maybe> { - return keyValues(ctx, j, aggregatableKeyValue) +function aggregatableValues(ctx: Context, j: Json): Maybe { + return typeSwitch(ctx, j, { + object: (ctx, j) => {console.log(j); return struct(ctx, j, { + aggregatableValue: (ctx, j) => keyValues(ctx, j, aggregatableKeyValue), + ...filterFields}).map((v) => [v])}, + list: (ctx, j) => array(ctx, j, (ctx, j) => struct(ctx, j, { + aggregatableValue: field('values', (ctx, j) => keyValues(ctx, j, aggregatableKeyValue)), + ...filterFields})) + } + ) } export type EventTriggerDatum = FilterPair & @@ -1276,39 +1291,39 @@ function aggregatableSourceRegistrationTime( return enumerated(ctx, j, AggregatableSourceRegistrationTime) } -function warnInconsistentAggregatableKeys(ctx: Context, t: Trigger): void { - const triggerDataKeys = new Set() - - ctx.scope('aggregatable_trigger_data', () => { - for (const [index, datum] of t.aggregatableTriggerData.entries()) { - ctx.scope(index, () => { - for (const key of datum.sourceKeys) { - triggerDataKeys.add(key) - - if (!t.aggregatableValues.has(key)) { - ctx.scope('source_keys', () => - ctx.warning( - `key "${key}" will never result in a contribution due to absence from aggregatable_values` - ) - ) - } - } - }) - } - }) - - ctx.scope('aggregatable_values', () => { - for (const key of t.aggregatableValues.keys()) { - if (!triggerDataKeys.has(key)) { - ctx.scope(key, () => - ctx.warning( - 'absence from aggregatable_trigger_data source_keys equivalent to presence with key_piece 0x0' - ) - ) - } - } - }) -} +// function warnInconsistentAggregatableKeys(ctx: Context, t: Trigger): void { +// const triggerDataKeys = new Set() + +// ctx.scope('aggregatable_trigger_data', () => { +// for (const [index, datum] of t.aggregatableTriggerData.entries()) { +// ctx.scope(index, () => { +// for (const key of datum.sourceKeys) { +// triggerDataKeys.add(key) + +// if (!t.aggregatableValues.has(key)) { +// ctx.scope('source_keys', () => +// ctx.warning( +// `key "${key}" will never result in a contribution due to absence from aggregatable_values` +// ) +// ) +// } +// } +// }) +// } +// }) + +// ctx.scope('aggregatable_values', () => { +// for (const key of t.aggregatableValues.keys()) { +// if (!triggerDataKeys.has(key)) { +// ctx.scope(key, () => +// ctx.warning( +// 'absence from aggregatable_trigger_data source_keys equivalent to presence with key_piece 0x0' +// ) +// ) +// } +// } +// }) +// } function triggerContextID( ctx: Context, @@ -1350,7 +1365,7 @@ export type Trigger = CommonDebug & aggregatableDedupKeys: AggregatableDedupKey[] aggregatableTriggerData: AggregatableTriggerDatum[] aggregatableSourceRegistrationTime: AggregatableSourceRegistrationTime - aggregatableValues: Map + aggregatableValues: AggregatableValue[] aggregationCoordinatorOrigin: string | null eventTriggerData: EventTriggerDatum[] triggerContextID: string | null @@ -1374,7 +1389,7 @@ function trigger(ctx: RegistrationContext, j: Json): Maybe { aggregatableValues: field( 'aggregatable_values', aggregatableValues, - new Map() + [] ), aggregatableDedupKeys: field( 'aggregatable_deduplication_keys', @@ -1397,7 +1412,7 @@ function trigger(ctx: RegistrationContext, j: Json): Maybe { ...filterFields, }) }) - .peek((t) => warnInconsistentAggregatableKeys(ctx, t)) + // .peek((t) => warnInconsistentAggregatableKeys(ctx, t)) } function validateJSON( From dc9f92a5e63405a9af52c8ea8336bdbc12247482 Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Tue, 23 Jan 2024 22:57:03 +0000 Subject: [PATCH 04/21] updates struct call to ignore warnings --- ts/src/header-validator/trigger.test.ts | 71 ++++++++++++------------ ts/src/header-validator/validate-json.ts | 17 +++--- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/ts/src/header-validator/trigger.test.ts b/ts/src/header-validator/trigger.test.ts index 292f2a602b..7a7f0e3b79 100644 --- a/ts/src/header-validator/trigger.test.ts +++ b/ts/src/header-validator/trigger.test.ts @@ -347,7 +347,7 @@ const testCases: jsontest.TestCase[] = [ expectedErrors: [ { path: ['aggregatable_values'], - msg: 'must be a list or object', + msg: 'must be an object or a list', }, ], }, @@ -403,7 +403,7 @@ const testCases: jsontest.TestCase[] = [ expectedErrors: [ { path: ['aggregatable_values',0,'values'], - msg: 'aggregatable values in a list must be defined in "values" field.', + msg: 'required', }, ], }, @@ -424,39 +424,40 @@ const testCases: jsontest.TestCase[] = [ ], }, - { - name: 'inconsistent-aggregatable-keys', - json: `{ - "aggregatable_trigger_data": [ - { - "key_piece": "0x1", - "source_keys": ["a"] - }, - { - "key_piece": "0x2", - "source_keys": ["b", "a"] - } - ], - "aggregatable_values": { - "b": 1, - "c": 2 - } - }`, - expectedWarnings: [ - { - path: ['aggregatable_trigger_data', 0, 'source_keys'], - msg: 'key "a" will never result in a contribution due to absence from aggregatable_values', - }, - { - path: ['aggregatable_trigger_data', 1, 'source_keys'], - msg: 'key "a" will never result in a contribution due to absence from aggregatable_values', - }, - { - path: ['aggregatable_values', 'c'], - msg: 'absence from aggregatable_trigger_data source_keys equivalent to presence with key_piece 0x0', - }, - ], - }, + // TODO(apasel422): Uncomment once respective function is updated. + // { + // name: 'inconsistent-aggregatable-keys', + // json: `{ + // "aggregatable_trigger_data": [ + // { + // "key_piece": "0x1", + // "source_keys": ["a"] + // }, + // { + // "key_piece": "0x2", + // "source_keys": ["b", "a"] + // } + // ], + // "aggregatable_values": { + // "b": 1, + // "c": 2 + // } + // }`, + // expectedWarnings: [ + // { + // path: ['aggregatable_trigger_data', 0, 'source_keys'], + // msg: 'key "a" will never result in a contribution due to absence from aggregatable_values', + // }, + // { + // path: ['aggregatable_trigger_data', 1, 'source_keys'], + // msg: 'key "a" will never result in a contribution due to absence from aggregatable_values', + // }, + // { + // path: ['aggregatable_values', 'c'], + // msg: 'absence from aggregatable_trigger_data source_keys equivalent to presence with key_piece 0x0', + // }, + // ], + // }, { name: 'debug-reporting-wrong-type', diff --git a/ts/src/header-validator/validate-json.ts b/ts/src/header-validator/validate-json.ts index 51114398eb..51d5e501aa 100644 --- a/ts/src/header-validator/validate-json.ts +++ b/ts/src/header-validator/validate-json.ts @@ -61,8 +61,6 @@ function struct( if (warnUnknown) { for (const key in d) { - console.log("timestamp: " + Date.now() +" - unknown") - console.log(d) ctx.scope(key, () => ctx.warning('unknown field')) } } @@ -82,7 +80,6 @@ function field( ctx.scope(name, () => { const v = d[name] if (v === undefined) { - console.log(name + " not found"); if (valueIfAbsent === undefined) { ctx.error('required') return None @@ -1217,14 +1214,13 @@ function aggregatableKeyValue( function aggregatableValues(ctx: Context, j: Json): Maybe { return typeSwitch(ctx, j, { - object: (ctx, j) => {console.log(j); return struct(ctx, j, { + object: (ctx, j) => struct(ctx, j, { aggregatableValue: (ctx, j) => keyValues(ctx, j, aggregatableKeyValue), - ...filterFields}).map((v) => [v])}, - list: (ctx, j) => array(ctx, j, (ctx, j) => struct(ctx, j, { - aggregatableValue: field('values', (ctx, j) => keyValues(ctx, j, aggregatableKeyValue)), - ...filterFields})) - } - ) + ...filterFields}, /*warnUnknown=*/false).map((v) => [v]), + list: (ctx, j) => array(ctx, j, (ctx, j) => struct(ctx, j, { + aggregatableValue: field('values', (ctx, j) => keyValues(ctx, j, aggregatableKeyValue)), + ...filterFields}, /*warnUnknown=*/false)) + }) } export type EventTriggerDatum = FilterPair & @@ -1291,6 +1287,7 @@ function aggregatableSourceRegistrationTime( return enumerated(ctx, j, AggregatableSourceRegistrationTime) } +// TODO(apasel422): Update with new AggregatableValue structure. // function warnInconsistentAggregatableKeys(ctx: Context, t: Trigger): void { // const triggerDataKeys = new Set() From 3c9be1810dfd1b8253a0a1ed5cce29956790c8cf Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Wed, 24 Jan 2024 15:26:45 +0000 Subject: [PATCH 05/21] formatting --- ts/src/header-validator/trigger.test.ts | 6 +- ts/src/header-validator/validate-json.ts | 106 +++++++++++++---------- 2 files changed, 63 insertions(+), 49 deletions(-) diff --git a/ts/src/header-validator/trigger.test.ts b/ts/src/header-validator/trigger.test.ts index 7a7f0e3b79..e6df1fa671 100644 --- a/ts/src/header-validator/trigger.test.ts +++ b/ts/src/header-validator/trigger.test.ts @@ -86,7 +86,7 @@ const testCases: jsontest.TestCase[] = [ aggregatableValue: new Map([['x', 5]]), positive: [], negative: [], - } + }, ], debugKey: 5n, debugReporting: true, @@ -402,7 +402,7 @@ const testCases: jsontest.TestCase[] = [ }`, expectedErrors: [ { - path: ['aggregatable_values',0,'values'], + path: ['aggregatable_values', 0, 'values'], msg: 'required', }, ], @@ -418,7 +418,7 @@ const testCases: jsontest.TestCase[] = [ }`, expectedErrors: [ { - path: ['aggregatable_values',0,'values'], + path: ['aggregatable_values', 0, 'values'], msg: 'must be an object', }, ], diff --git a/ts/src/header-validator/validate-json.ts b/ts/src/header-validator/validate-json.ts index 51d5e501aa..f508c282a5 100644 --- a/ts/src/header-validator/validate-json.ts +++ b/ts/src/header-validator/validate-json.ts @@ -1214,13 +1214,32 @@ function aggregatableKeyValue( function aggregatableValues(ctx: Context, j: Json): Maybe { return typeSwitch(ctx, j, { - object: (ctx, j) => struct(ctx, j, { - aggregatableValue: (ctx, j) => keyValues(ctx, j, aggregatableKeyValue), - ...filterFields}, /*warnUnknown=*/false).map((v) => [v]), - list: (ctx, j) => array(ctx, j, (ctx, j) => struct(ctx, j, { - aggregatableValue: field('values', (ctx, j) => keyValues(ctx, j, aggregatableKeyValue)), - ...filterFields}, /*warnUnknown=*/false)) - }) + object: (ctx, j) => + struct( + ctx, + j, + { + aggregatableValue: (ctx, j) => + keyValues(ctx, j, aggregatableKeyValue), + ...filterFields, + }, + /*warnUnknown=*/ false + ).map((v) => [v]), + list: (ctx, j) => + array(ctx, j, (ctx, j) => + struct( + ctx, + j, + { + aggregatableValue: field('values', (ctx, j) => + keyValues(ctx, j, aggregatableKeyValue) + ), + ...filterFields, + }, + /*warnUnknown=*/ false + ) + ), + }) } export type EventTriggerDatum = FilterPair & @@ -1369,47 +1388,42 @@ export type Trigger = CommonDebug & } function trigger(ctx: RegistrationContext, j: Json): Maybe { - return object(ctx, j) - .map((j) => { - const aggregatableSourceRegTimeVal = field( - 'aggregatable_source_registration_time', - aggregatableSourceRegistrationTime, - AggregatableSourceRegistrationTime.exclude - )(ctx, j) + return object(ctx, j).map((j) => { + const aggregatableSourceRegTimeVal = field( + 'aggregatable_source_registration_time', + aggregatableSourceRegistrationTime, + AggregatableSourceRegistrationTime.exclude + )(ctx, j) - return struct(ctx, j, { - aggregatableTriggerData: field( - 'aggregatable_trigger_data', - aggregatableTriggerData, - [] - ), - aggregatableValues: field( - 'aggregatable_values', - aggregatableValues, - [] - ), - aggregatableDedupKeys: field( - 'aggregatable_deduplication_keys', - aggregatableDedupKeys, - [] - ), - aggregatableSourceRegistrationTime: () => aggregatableSourceRegTimeVal, - aggregationCoordinatorOrigin: field( - 'aggregation_coordinator_origin', - suitableOrigin, - null - ), - eventTriggerData: field('event_trigger_data', eventTriggerData, []), - triggerContextID: field( - 'trigger_context_id', - (ctx, j) => triggerContextID(ctx, j, aggregatableSourceRegTimeVal), - null - ), - ...commonDebugFields, - ...filterFields, - }) + return struct(ctx, j, { + aggregatableTriggerData: field( + 'aggregatable_trigger_data', + aggregatableTriggerData, + [] + ), + aggregatableValues: field('aggregatable_values', aggregatableValues, []), + aggregatableDedupKeys: field( + 'aggregatable_deduplication_keys', + aggregatableDedupKeys, + [] + ), + aggregatableSourceRegistrationTime: () => aggregatableSourceRegTimeVal, + aggregationCoordinatorOrigin: field( + 'aggregation_coordinator_origin', + suitableOrigin, + null + ), + eventTriggerData: field('event_trigger_data', eventTriggerData, []), + triggerContextID: field( + 'trigger_context_id', + (ctx, j) => triggerContextID(ctx, j, aggregatableSourceRegTimeVal), + null + ), + ...commonDebugFields, + ...filterFields, }) - // .peek((t) => warnInconsistentAggregatableKeys(ctx, t)) + }) + // .peek((t) => warnInconsistentAggregatableKeys(ctx, t)) } function validateJSON( From d4e0ace5c8348ac58cb4b92a29e9480313cbb54b Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Wed, 24 Jan 2024 20:14:34 +0000 Subject: [PATCH 06/21] Renames struct, clean up algorithms --- index.bs | 85 +++++----- ts/src/header-validator/trigger.test.ts | 10 +- ts/src/header-validator/validate-json.ts | 189 ++++++++++++----------- 3 files changed, 147 insertions(+), 137 deletions(-) diff --git a/index.bs b/index.bs index 951a4edf97..47ff544b89 100644 --- a/index.bs +++ b/index.bs @@ -797,11 +797,11 @@ An aggregatable trigger data is a [=struct=] with the following items: -

Aggregatable value

+

Aggregatable values configuration

-An aggregatable value is a [=struct=] with the following items: +An aggregatable values configuration is a [=struct=] with the following items: -
+
: values :: An [=ordered map=] whose [=map/key|keys=] are [=strings=] and whose [=map/value|values=] are non-negative 32-bit integers. : filters @@ -882,8 +882,8 @@ An attribution trigger is a [=struct=] with the following items: :: A [=set=] of [=event-level trigger configuration=]. : aggregatable trigger data :: A [=list=] of [=aggregatable trigger data=]. -: aggregatable values -:: A [=list=] of [=aggregatable value=]. +: aggregatable values configurations +:: A [=list=] of [=aggregatable values configuration=]. : aggregatable dedup keys :: A [=list=] of [=aggregatable dedup key=]. : verifications @@ -1209,7 +1209,7 @@ controls the maximum [=map/size=] of an [=attribution source=]'s Max length per aggregation key identifier is a positive integer that controls the maximum [=string/length=] of an [=attribution source=]'s [=attribution source/aggregation keys=]'s -[=map/keys=], an [=attribution trigger=]'s [=attribution trigger/aggregatable values=]'s [=map/keys=], +[=map/keys=], an [=attribution trigger=]'s [=attribution trigger/aggregatable values configurations=]'s [=map/keys=], and an [=aggregatable trigger data=]'s [=aggregatable trigger data/source keys=]'s [=set/items=]. Its value is 25. @@ -2414,31 +2414,32 @@ To parse aggregatable trigger data given an [=ordered map=] |map|: To parse aggregatable key-values given an [=ordered map=] |map|: -1. [=map/iterate|For each=] |key| → |value| of |values|: +1. [=map/iterate|For each=] |key| → |value| of |map|: 1. If |key|'s [=string/length=] is greater than the [=max length per aggregation key identifier=], return null. 1. If |value| is not an integer, return null. 1. If |value| is less than or equal to 0, return null. 1. If |value| is greater than [=allowed aggregatable budget per source=], return null. -1. Return |values|. +1. Return |map|. To parse aggregatable values given an [=ordered map=] |map|: 1. If |map|["`aggregatable_values`"] does not [=map/exist=], return «[]». 1. Let |values| be |map|["`aggregatable_values`"]. 1. If |values| is not an [=ordered map=] or a [=list=], return null. -1. Let |aggregatableValues| be a [=list=] of [=aggregatable value=], initially empty. +1. Let |aggregatableValuesConfigurations| be a [=list=] of [=aggregatable values configuration=], initially empty. 1. If |values| is an [=ordered map=]: 1. Let |aggregatableKeyValues| be the result of running [=parse aggregatable key-values=] with |values|. - 1. Let |aggregatableValue| be a new [=aggregatable value=] with the items: - : [=aggregatable value/values=] + 1. If |aggregatableKeyValues| is null, return null. + 1. Let |aggregatableValuesConfiguration| be a new [=aggregatable values configuration=] with the items: + : [=aggregatable values configuration/values=] :: |aggregatableKeyValues| - : [=aggregatable value/filters=] + : [=aggregatable values configuration/filters=] :: «[]» - : [=aggregatable value/negated filters=] + : [=aggregatable values configuration/negated filters=] :: «[]» - 1. [=list/Append=] |aggregatableValue| to |aggregatableValues|. - 1. Return |aggregatableValues| + 1. [=list/Append=] |aggregatableValuesConfiguration| to |aggregatableValuesConfigurations|. + 1. Return |aggregatableValuesConfigurations|. 1. [=list/iterate|For each=] |value| of |values|: 1. If |value| is not an [=ordered map=], return null. 1. If |value|["`values`"] does not [=map/exist=], return null. @@ -2447,15 +2448,15 @@ To parse aggregatable values given an [=ordered map=] |map|: 1. Let |filterPair| be the result of running [=parse a filter pair=] with |aggregatableKeyValues|. 1. If |filterPair| is null, return null. - 1. Let |aggregatableValue| be a new [=aggregatable value=] with the items: - : [=aggregatable value/values=] + 1. Let |aggregatableValuesConfiguration| be a new [=aggregatable values configuration=] with the items: + : [=aggregatable values configuration/values=] :: |aggregatableKeyValues| - : [=aggregatable value/filters=] + : [=aggregatable values configuration/filters=] :: |filterPair|[0] - : [=aggregatable value/negated filters=] + : [=aggregatable values configuration/negated filters=] :: |filterPair|[1] - 1. [=list/Append=] |aggregatableValue| to |aggregatableValues|. -1. Return |aggregatableValues|. + 1. [=list/Append=] |aggregatableValuesConfiguration| to |aggregatableValuesConfigurations|. +1. Return |aggregatableValuesConfigurations|. To parse aggregatable dedup keys given an [=ordered map=] |map|: @@ -2495,8 +2496,8 @@ and a [=moment=] |triggerTime|: 1. Let |aggregatableTriggerData| be the result of running [=parse aggregatable trigger data=] with |value|. 1. If |aggregatableTriggerData| is null, return null. -1. Let |aggregatableValues| be the result of running [=parse aggregatable values=] with |value|. -1. If |aggregatableValues| is null, return null. +1. Let |aggregatableValuesConfigurations| be the result of running [=parse aggregatable values=] with |value|. +1. If |aggregatableValuesConfigurations| is null, return null. 1. Let |aggregatableDedupKeys| be the result of running [=parse aggregatable dedup keys=] with |value|. 1. If |aggregatableDedupKeys| is null, return null. @@ -2548,8 +2549,8 @@ and a [=moment=] |triggerTime|: :: |eventTriggers| : [=attribution trigger/aggregatable trigger data=] :: |aggregatableTriggerData| - : [=attribution trigger/aggregatable values=] - :: |aggregatableValues| + : [=attribution trigger/aggregatable values configurations=] + :: |aggregatableValuesConfigurations| : [=attribution trigger/aggregatable dedup keys=] :: |aggregatableDedupKeys| : [=attribution trigger/verifications=] @@ -2708,23 +2709,23 @@ To create [=aggregatable contributions=] given an [=attribution sourc 1. If |aggregationKeys|[|sourceKey|] does not [=map/exist=], [=iteration/continue=]. 1. [=map/Set=] |aggregationKeys|[|sourceKey|] to |aggregationKeys|[|sourceKey|] bitwise-OR |triggerData|'s [=aggregatable trigger data/key piece=]. -1. Let |aggregatableValues| be |trigger|'s [=attribution trigger/aggregatable values=]. +1. Let |aggregatableValuesConfigurations| be |trigger|'s [=attribution trigger/aggregatable values configurations=]. 1. Let |contributions| be a new empty [=list=]. -1. [=list/iterate|For each=] |aggregatableValue| of |aggregatableValues|: - 1. [=map/iterate|For each=] |id| → |key| of |aggregationKeys|: - 1. If |aggregatableValue|'s [=aggregatable value/values=][|id|] does not [=map/exist=] or if - the result of running [=match an attribution source's filter data against filters and negated filters=] with - |source|, |aggregatableValue|'s [=aggregatable value/filters=], - |aggregatableValue|'s [=aggregatable value/negated filters=], and - |trigger|'s [=attribution trigger/trigger time=] - is false, [=iteration/continue=]. - 1. Let |contribution| be a new [=aggregatable contribution=] with the items: - : [=aggregatable contribution/key=] - :: |key| - : [=aggregatable contribution/value=] - :: |aggregatableValues|[|id|] - 1. [=list/Append=] |contribution| to |contributions|. - 1. If |contributions| is not [=list/is empty|empty=], [=iteration/Break=]. +1. [=list/iterate|For each=] |aggregatableValuesConfiguration| of |aggregatableValuesConfigurations|: + 1. If the result of running [=match an attribution source's filter data against filters and negated filters=] with + |source|, |aggregatableValuesConfiguration|'s [=aggregatable values configuration/filters=], + |aggregatableValuesConfiguration|'s [=aggregatable values configuration/negated filters=], and + |trigger|'s [=attribution trigger/trigger time=] is true: + 1. [=map/iterate|For each=] |id| → |key| of |aggregationKeys|: + 1. Let |aggregatableValues| be |aggregatableValuesConfiguration|'s [=aggregatable values configuration/values=]. + 1. If |aggregatableValues|[|id|] does not [=map/exist=], [=iteration/continue=]. + 1. Let |contribution| be a new [=aggregatable contribution=] with the items: + : [=aggregatable contribution/key=] + :: |key| + : [=aggregatable contribution/value=] + :: |aggregatableValues|[|id|] + 1. [=list/Append=] |contribution| to |contributions|. + 1. If |contributions| is not [=list/is empty|empty=], [=iteration/Break=]. 1. Return |contributions|.

Can source create aggregatable contributions

@@ -3071,7 +3072,7 @@ To check if an [=attribution trigger=] contains aggregatable data giv run the following steps: 1. If |trigger|'s [=attribution trigger/aggregatable trigger data=] is not [=list/is empty|empty=], return true. -1. If |trigger|'s [=attribution trigger/aggregatable values=] is not [=map/is empty|empty=], return true. +1. If |trigger|'s [=attribution trigger/aggregatable values configurations=] is not [=map/is empty|empty=], return true. 1. Return false. To trigger attribution given an [=attribution trigger=] |trigger|, run the following steps: diff --git a/ts/src/header-validator/trigger.test.ts b/ts/src/header-validator/trigger.test.ts index e6df1fa671..96ed692b1c 100644 --- a/ts/src/header-validator/trigger.test.ts +++ b/ts/src/header-validator/trigger.test.ts @@ -81,9 +81,9 @@ const testCases: jsontest.TestCase[] = [ sourceKeys: new Set(['x']), }, ], - aggregatableValues: [ + aggregatableValuesConfigurations: [ { - aggregatableValue: new Map([['x', 5]]), + values: new Map([['x', 5]]), positive: [], negative: [], }, @@ -406,6 +406,12 @@ const testCases: jsontest.TestCase[] = [ msg: 'required', }, ], + expectedWarnings: [ + { + msg: 'unknown field', + path: ['aggregatable_values', 0, 'a'], + }, + ], }, { name: 'aggregatable-values-list-wrong-type', diff --git a/ts/src/header-validator/validate-json.ts b/ts/src/header-validator/validate-json.ts index f508c282a5..1ce25b23bc 100644 --- a/ts/src/header-validator/validate-json.ts +++ b/ts/src/header-validator/validate-json.ts @@ -1194,8 +1194,8 @@ function aggregatableTriggerData( ) } -export type AggregatableValue = FilterPair & { - aggregatableValue: Map +export type AggregatableValuesConfiguration = FilterPair & { + values: Map } function aggregatableKeyValue( @@ -1212,32 +1212,28 @@ function aggregatableKeyValue( ) } -function aggregatableValues(ctx: Context, j: Json): Maybe { +function aggregatableKeyValues( + ctx: Context, + j: Json +): Maybe> { + return keyValues(ctx, j, aggregatableKeyValue) +} + +function aggregatableValuesConfigurations( + ctx: Context, + j: Json +): Maybe { return typeSwitch(ctx, j, { object: (ctx, j) => - struct( - ctx, - j, - { - aggregatableValue: (ctx, j) => - keyValues(ctx, j, aggregatableKeyValue), - ...filterFields, - }, - /*warnUnknown=*/ false - ).map((v) => [v]), + aggregatableKeyValues(ctx, j).map((values) => [ + { values, positive: [], negative: [] }, + ]), list: (ctx, j) => array(ctx, j, (ctx, j) => - struct( - ctx, - j, - { - aggregatableValue: field('values', (ctx, j) => - keyValues(ctx, j, aggregatableKeyValue) - ), - ...filterFields, - }, - /*warnUnknown=*/ false - ) + struct(ctx, j, { + values: field('values', (ctx, j) => aggregatableKeyValues(ctx, j)), + ...filterFields, + }) ), }) } @@ -1306,40 +1302,42 @@ function aggregatableSourceRegistrationTime( return enumerated(ctx, j, AggregatableSourceRegistrationTime) } -// TODO(apasel422): Update with new AggregatableValue structure. -// function warnInconsistentAggregatableKeys(ctx: Context, t: Trigger): void { -// const triggerDataKeys = new Set() - -// ctx.scope('aggregatable_trigger_data', () => { -// for (const [index, datum] of t.aggregatableTriggerData.entries()) { -// ctx.scope(index, () => { -// for (const key of datum.sourceKeys) { -// triggerDataKeys.add(key) - -// if (!t.aggregatableValues.has(key)) { -// ctx.scope('source_keys', () => -// ctx.warning( -// `key "${key}" will never result in a contribution due to absence from aggregatable_values` -// ) -// ) -// } -// } -// }) -// } -// }) - -// ctx.scope('aggregatable_values', () => { -// for (const key of t.aggregatableValues.keys()) { -// if (!triggerDataKeys.has(key)) { -// ctx.scope(key, () => -// ctx.warning( -// 'absence from aggregatable_trigger_data source_keys equivalent to presence with key_piece 0x0' -// ) -// ) -// } -// } -// }) -// } +// TODO(apasel422): Update with new AggregatableValuesConfiguration structure. +function warnInconsistentAggregatableKeys(_ctx: Context, _t: Trigger): void { + /* +const triggerDataKeys = new Set() + + ctx.scope('aggregatable_trigger_data', () => { + for (const [index, datum] of t.aggregatableTriggerData.entries()) { + ctx.scope(index, () => { + for (const key of datum.sourceKeys) { + triggerDataKeys.add(key) + + if (!t.aggregatableValues.has(key)) { + ctx.scope('source_keys', () => + ctx.warning( + `key "${key}" will never result in a contribution due to absence from aggregatable_values` + ) + ) + } + } + }) + } + }) + + ctx.scope('aggregatable_values', () => { + for (const key of t.aggregatableValues.keys()) { + if (!triggerDataKeys.has(key)) { + ctx.scope(key, () => + ctx.warning( + 'absence from aggregatable_trigger_data source_keys equivalent to presence with key_piece 0x0' + ) + ) + } + } + }) +*/ +} function triggerContextID( ctx: Context, @@ -1381,49 +1379,54 @@ export type Trigger = CommonDebug & aggregatableDedupKeys: AggregatableDedupKey[] aggregatableTriggerData: AggregatableTriggerDatum[] aggregatableSourceRegistrationTime: AggregatableSourceRegistrationTime - aggregatableValues: AggregatableValue[] + aggregatableValuesConfigurations: AggregatableValuesConfiguration[] aggregationCoordinatorOrigin: string | null eventTriggerData: EventTriggerDatum[] triggerContextID: string | null } function trigger(ctx: RegistrationContext, j: Json): Maybe { - return object(ctx, j).map((j) => { - const aggregatableSourceRegTimeVal = field( - 'aggregatable_source_registration_time', - aggregatableSourceRegistrationTime, - AggregatableSourceRegistrationTime.exclude - )(ctx, j) + return object(ctx, j) + .map((j) => { + const aggregatableSourceRegTimeVal = field( + 'aggregatable_source_registration_time', + aggregatableSourceRegistrationTime, + AggregatableSourceRegistrationTime.exclude + )(ctx, j) - return struct(ctx, j, { - aggregatableTriggerData: field( - 'aggregatable_trigger_data', - aggregatableTriggerData, - [] - ), - aggregatableValues: field('aggregatable_values', aggregatableValues, []), - aggregatableDedupKeys: field( - 'aggregatable_deduplication_keys', - aggregatableDedupKeys, - [] - ), - aggregatableSourceRegistrationTime: () => aggregatableSourceRegTimeVal, - aggregationCoordinatorOrigin: field( - 'aggregation_coordinator_origin', - suitableOrigin, - null - ), - eventTriggerData: field('event_trigger_data', eventTriggerData, []), - triggerContextID: field( - 'trigger_context_id', - (ctx, j) => triggerContextID(ctx, j, aggregatableSourceRegTimeVal), - null - ), - ...commonDebugFields, - ...filterFields, + return struct(ctx, j, { + aggregatableTriggerData: field( + 'aggregatable_trigger_data', + aggregatableTriggerData, + [] + ), + aggregatableValuesConfigurations: field( + 'aggregatable_values', + aggregatableValuesConfigurations, + [] + ), + aggregatableDedupKeys: field( + 'aggregatable_deduplication_keys', + aggregatableDedupKeys, + [] + ), + aggregatableSourceRegistrationTime: () => aggregatableSourceRegTimeVal, + aggregationCoordinatorOrigin: field( + 'aggregation_coordinator_origin', + suitableOrigin, + null + ), + eventTriggerData: field('event_trigger_data', eventTriggerData, []), + triggerContextID: field( + 'trigger_context_id', + (ctx, j) => triggerContextID(ctx, j, aggregatableSourceRegTimeVal), + null + ), + ...commonDebugFields, + ...filterFields, + }) }) - }) - // .peek((t) => warnInconsistentAggregatableKeys(ctx, t)) + .peek((t) => warnInconsistentAggregatableKeys(ctx, t)) } function validateJSON( From 8ff952b781e91963e86e7b2b1e837713d9a53a2e Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Wed, 24 Jan 2024 20:33:11 +0000 Subject: [PATCH 07/21] nits --- index.bs | 2 +- ts/src/header-validator/trigger.test.ts | 68 +++++++++++++------------ 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/index.bs b/index.bs index 47ff544b89..27fb601eea 100644 --- a/index.bs +++ b/index.bs @@ -2725,7 +2725,7 @@ To create [=aggregatable contributions=] given an [=attribution sourc : [=aggregatable contribution/value=] :: |aggregatableValues|[|id|] 1. [=list/Append=] |contribution| to |contributions|. - 1. If |contributions| is not [=list/is empty|empty=], [=iteration/Break=]. + 1. If |contributions| is not [=list/is empty|empty=], [=iteration/break=]. 1. Return |contributions|.

Can source create aggregatable contributions

diff --git a/ts/src/header-validator/trigger.test.ts b/ts/src/header-validator/trigger.test.ts index 96ed692b1c..2442314df1 100644 --- a/ts/src/header-validator/trigger.test.ts +++ b/ts/src/header-validator/trigger.test.ts @@ -431,39 +431,41 @@ const testCases: jsontest.TestCase[] = [ }, // TODO(apasel422): Uncomment once respective function is updated. - // { - // name: 'inconsistent-aggregatable-keys', - // json: `{ - // "aggregatable_trigger_data": [ - // { - // "key_piece": "0x1", - // "source_keys": ["a"] - // }, - // { - // "key_piece": "0x2", - // "source_keys": ["b", "a"] - // } - // ], - // "aggregatable_values": { - // "b": 1, - // "c": 2 - // } - // }`, - // expectedWarnings: [ - // { - // path: ['aggregatable_trigger_data', 0, 'source_keys'], - // msg: 'key "a" will never result in a contribution due to absence from aggregatable_values', - // }, - // { - // path: ['aggregatable_trigger_data', 1, 'source_keys'], - // msg: 'key "a" will never result in a contribution due to absence from aggregatable_values', - // }, - // { - // path: ['aggregatable_values', 'c'], - // msg: 'absence from aggregatable_trigger_data source_keys equivalent to presence with key_piece 0x0', - // }, - // ], - // }, + /* + { + name: 'inconsistent-aggregatable-keys', + json: `{ + "aggregatable_trigger_data": [ + { + "key_piece": "0x1", + "source_keys": ["a"] + }, + { + "key_piece": "0x2", + "source_keys": ["b", "a"] + } + ], + "aggregatable_values": { + "b": 1, + "c": 2 + } + }`, + expectedWarnings: [ + { + path: ['aggregatable_trigger_data', 0, 'source_keys'], + msg: 'key "a" will never result in a contribution due to absence from aggregatable_values', + }, + { + path: ['aggregatable_trigger_data', 1, 'source_keys'], + msg: 'key "a" will never result in a contribution due to absence from aggregatable_values', + }, + { + path: ['aggregatable_values', 'c'], + msg: 'absence from aggregatable_trigger_data source_keys equivalent to presence with key_piece 0x0', + }, + ], + }, + */ { name: 'debug-reporting-wrong-type', From 282caf1ed08ab5ddd948cc27f206224a814213de Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Wed, 24 Jan 2024 21:15:02 +0000 Subject: [PATCH 08/21] key identifer constant fix --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 27fb601eea..795442c4ff 100644 --- a/index.bs +++ b/index.bs @@ -1209,7 +1209,7 @@ controls the maximum [=map/size=] of an [=attribution source=]'s Max length per aggregation key identifier is a positive integer that controls the maximum [=string/length=] of an [=attribution source=]'s [=attribution source/aggregation keys=]'s -[=map/keys=], an [=attribution trigger=]'s [=attribution trigger/aggregatable values configurations=]'s [=map/keys=], +[=map/keys=], an [=attribution trigger=]'s [=attribution trigger/aggregatable values configurations=]'s [=aggregatable values configuration/values=]'s [=map/keys=], and an [=aggregatable trigger data=]'s [=aggregatable trigger data/source keys=]'s [=set/items=]. Its value is 25. From bca1a16a3e72fbfb32d3ddf27a2ce6265d9f973a Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Thu, 25 Jan 2024 15:56:03 +0000 Subject: [PATCH 09/21] Aligns with dedup key algo --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 795442c4ff..342f9b6508 100644 --- a/index.bs +++ b/index.bs @@ -2715,9 +2715,9 @@ To create [=aggregatable contributions=] given an [=attribution sourc 1. If the result of running [=match an attribution source's filter data against filters and negated filters=] with |source|, |aggregatableValuesConfiguration|'s [=aggregatable values configuration/filters=], |aggregatableValuesConfiguration|'s [=aggregatable values configuration/negated filters=], and - |trigger|'s [=attribution trigger/trigger time=] is true: + |trigger|'s [=attribution trigger/trigger time=] is true: + 1. Let |aggregatableValues| be |aggregatableValuesConfiguration|'s [=aggregatable values configuration/values=]. 1. [=map/iterate|For each=] |id| → |key| of |aggregationKeys|: - 1. Let |aggregatableValues| be |aggregatableValuesConfiguration|'s [=aggregatable values configuration/values=]. 1. If |aggregatableValues|[|id|] does not [=map/exist=], [=iteration/continue=]. 1. Let |contribution| be a new [=aggregatable contribution=] with the items: : [=aggregatable contribution/key=] @@ -2725,7 +2725,7 @@ To create [=aggregatable contributions=] given an [=attribution sourc : [=aggregatable contribution/value=] :: |aggregatableValues|[|id|] 1. [=list/Append=] |contribution| to |contributions|. - 1. If |contributions| is not [=list/is empty|empty=], [=iteration/break=]. + 1. [=iteration/Break=]. 1. Return |contributions|.

Can source create aggregatable contributions

From cd0e5e27f4491145d72a811521fde830a927fd80 Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Thu, 25 Jan 2024 16:29:06 +0000 Subject: [PATCH 10/21] adds sub algorithm, refactoring --- index.bs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/index.bs b/index.bs index 342f9b6508..a283630127 100644 --- a/index.bs +++ b/index.bs @@ -2695,6 +2695,21 @@ Given an [=attribution trigger=] |trigger|, an [=attribution source=]

Creating aggregatable contributions

+To match [=aggregatable contributions=] to [=attribution source/aggregation keys=] given + an [=ordered map=] |aggregationKeys| and an [=ordered map=] |aggregatableValues|, + run the following steps: + +1. Let |contributions| be an empty list. +1. [=map/iterate|For each=] |id| → |key| of |aggregationKeys|: + 1. If |aggregatableValues|[|id|] does not [=map/exist=], [=iteration/continue=]. + 1. Let |contribution| be a new [=aggregatable contribution=] with the items: + : [=aggregatable contribution/key=] + :: |key| + : [=aggregatable contribution/value=] + :: |aggregatableValues|[|id|] + 1. [=list/Append=] |contribution| to |contributions|. +1. Return |contributions|. + To create [=aggregatable contributions=] given an [=attribution source=] |source| and an [=attribution trigger=] |trigger|, run the following steps: @@ -2710,23 +2725,14 @@ To create [=aggregatable contributions=] given an [=attribution sourc 1. [=map/Set=] |aggregationKeys|[|sourceKey|] to |aggregationKeys|[|sourceKey|] bitwise-OR |triggerData|'s [=aggregatable trigger data/key piece=]. 1. Let |aggregatableValuesConfigurations| be |trigger|'s [=attribution trigger/aggregatable values configurations=]. -1. Let |contributions| be a new empty [=list=]. 1. [=list/iterate|For each=] |aggregatableValuesConfiguration| of |aggregatableValuesConfigurations|: 1. If the result of running [=match an attribution source's filter data against filters and negated filters=] with |source|, |aggregatableValuesConfiguration|'s [=aggregatable values configuration/filters=], |aggregatableValuesConfiguration|'s [=aggregatable values configuration/negated filters=], and |trigger|'s [=attribution trigger/trigger time=] is true: - 1. Let |aggregatableValues| be |aggregatableValuesConfiguration|'s [=aggregatable values configuration/values=]. - 1. [=map/iterate|For each=] |id| → |key| of |aggregationKeys|: - 1. If |aggregatableValues|[|id|] does not [=map/exist=], [=iteration/continue=]. - 1. Let |contribution| be a new [=aggregatable contribution=] with the items: - : [=aggregatable contribution/key=] - :: |key| - : [=aggregatable contribution/value=] - :: |aggregatableValues|[|id|] - 1. [=list/Append=] |contribution| to |contributions|. - 1. [=iteration/Break=]. -1. Return |contributions|. + 1. Return the result of running [=match aggregatable contributions to aggregation keys=] with + |aggregationKeys| and |aggregatableValuesConfiguration|'s [=aggregatable values configuration/values=]. +1. Return «[]».

Can source create aggregatable contributions

From 9f446577471e165fe6bbbfba246be0078cc0892a Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Fri, 26 Jan 2024 17:17:17 +0000 Subject: [PATCH 11/21] fix empty list, var --- index.bs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/index.bs b/index.bs index a283630127..c95cbb07ad 100644 --- a/index.bs +++ b/index.bs @@ -2424,7 +2424,7 @@ To parse aggregatable key-values given an [=ordered map=] |map|: To parse aggregatable values given an [=ordered map=] |map|: -1. If |map|["`aggregatable_values`"] does not [=map/exist=], return «[]». +1. If |map|["`aggregatable_values`"] does not [=map/exist=], return a new [=list/is empty|empty=] [=list=]. 1. Let |values| be |map|["`aggregatable_values`"]. 1. If |values| is not an [=ordered map=] or a [=list=], return null. 1. Let |aggregatableValuesConfigurations| be a [=list=] of [=aggregatable values configuration=], initially empty. @@ -2435,9 +2435,9 @@ To parse aggregatable values given an [=ordered map=] |map|: : [=aggregatable values configuration/values=] :: |aggregatableKeyValues| : [=aggregatable values configuration/filters=] - :: «[]» + :: «» : [=aggregatable values configuration/negated filters=] - :: «[]» + :: «» 1. [=list/Append=] |aggregatableValuesConfiguration| to |aggregatableValuesConfigurations|. 1. Return |aggregatableValuesConfigurations|. 1. [=list/iterate|For each=] |value| of |values|: @@ -2446,7 +2446,7 @@ To parse aggregatable values given an [=ordered map=] |map|: 1. Let |aggregatableKeyValues| be the result of running [=parse aggregatable key-values=] with |value|["`values`"]. 1. If |aggregatableKeyValues| is null, return null. 1. Let |filterPair| be the result of running [=parse a filter pair=] with - |aggregatableKeyValues|. + |value|. 1. If |filterPair| is null, return null. 1. Let |aggregatableValuesConfiguration| be a new [=aggregatable values configuration=] with the items: : [=aggregatable values configuration/values=] @@ -2732,7 +2732,7 @@ To create [=aggregatable contributions=] given an [=attribution sourc |trigger|'s [=attribution trigger/trigger time=] is true: 1. Return the result of running [=match aggregatable contributions to aggregation keys=] with |aggregationKeys| and |aggregatableValuesConfiguration|'s [=aggregatable values configuration/values=]. -1. Return «[]». +1. Return a new [=list/is empty|empty=] [=list=].

Can source create aggregatable contributions

From 582ef72997d301e5944ddcf5af7165d598cbb703 Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla <32398678+ThomasQuesadilla@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:18:28 -0500 Subject: [PATCH 12/21] Apply suggestions from code review Co-authored-by: Andrew Paseltiner --- index.bs | 4 ++-- ts/src/header-validator/validate-json.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index c95cbb07ad..e2e8e207f4 100644 --- a/index.bs +++ b/index.bs @@ -2699,7 +2699,7 @@ To match [=aggregatable contributions=] to [=attribution source/aggregation an [=ordered map=] |aggregationKeys| and an [=ordered map=] |aggregatableValues|, run the following steps: -1. Let |contributions| be an empty list. +1. Let |contributions| be an empty [=list=]. 1. [=map/iterate|For each=] |id| → |key| of |aggregationKeys|: 1. If |aggregatableValues|[|id|] does not [=map/exist=], [=iteration/continue=]. 1. Let |contribution| be a new [=aggregatable contribution=] with the items: @@ -3078,7 +3078,7 @@ To check if an [=attribution trigger=] contains aggregatable data giv run the following steps: 1. If |trigger|'s [=attribution trigger/aggregatable trigger data=] is not [=list/is empty|empty=], return true. -1. If |trigger|'s [=attribution trigger/aggregatable values configurations=] is not [=map/is empty|empty=], return true. +1. If |trigger|'s [=attribution trigger/aggregatable values configurations=] is not [=list/is empty|empty=], return true. 1. Return false. To trigger attribution given an [=attribution trigger=] |trigger|, run the following steps: diff --git a/ts/src/header-validator/validate-json.ts b/ts/src/header-validator/validate-json.ts index 1ce25b23bc..46f0033dc9 100644 --- a/ts/src/header-validator/validate-json.ts +++ b/ts/src/header-validator/validate-json.ts @@ -1231,7 +1231,7 @@ function aggregatableValuesConfigurations( list: (ctx, j) => array(ctx, j, (ctx, j) => struct(ctx, j, { - values: field('values', (ctx, j) => aggregatableKeyValues(ctx, j)), + values: field('values', aggregatableKeyValues), ...filterFields, }) ), From b8104209d7dc9fc75f8a8e50c8db758ff132a6ad Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Fri, 26 Jan 2024 21:58:32 +0000 Subject: [PATCH 13/21] specified individual element of aggregation values --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index e2e8e207f4..5b84bf7384 100644 --- a/index.bs +++ b/index.bs @@ -1209,7 +1209,7 @@ controls the maximum [=map/size=] of an [=attribution source=]'s Max length per aggregation key identifier is a positive integer that controls the maximum [=string/length=] of an [=attribution source=]'s [=attribution source/aggregation keys=]'s -[=map/keys=], an [=attribution trigger=]'s [=attribution trigger/aggregatable values configurations=]'s [=aggregatable values configuration/values=]'s [=map/keys=], +[=map/keys=], an [=attribution trigger=]'s [=attribution trigger/aggregatable values configurations=]'s item's [=aggregatable values configuration/values=]'s [=map/keys=], and an [=aggregatable trigger data=]'s [=aggregatable trigger data/source keys=]'s [=set/items=]. Its value is 25. From 139c1e5d0a25c03cef867c11f6fc15de17728810 Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Fri, 26 Jan 2024 22:00:30 +0000 Subject: [PATCH 14/21] formatting --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 5b84bf7384..315856c84b 100644 --- a/index.bs +++ b/index.bs @@ -1209,9 +1209,9 @@ controls the maximum [=map/size=] of an [=attribution source=]'s Max length per aggregation key identifier is a positive integer that controls the maximum [=string/length=] of an [=attribution source=]'s [=attribution source/aggregation keys=]'s -[=map/keys=], an [=attribution trigger=]'s [=attribution trigger/aggregatable values configurations=]'s item's [=aggregatable values configuration/values=]'s [=map/keys=], -and an [=aggregatable trigger data=]'s [=aggregatable trigger data/source keys=]'s [=set/items=]. -Its value is 25. +[=map/keys=], an [=attribution trigger=]'s [=attribution trigger/aggregatable values configurations=]'s item's +[=aggregatable values configuration/values=]'s [=map/keys=], and an [=aggregatable trigger data=]'s +[=aggregatable trigger data/source keys=]'s [=set/items=]. Its value is 25. Default trigger data cardinality is a [=map=] that controls the valid range of [=event-level trigger configuration/trigger data=]. From 680462f8f87300e999084dafdb6222a2f6c74e3d Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Wed, 31 Jan 2024 21:14:40 +0000 Subject: [PATCH 15/21] Update filter name --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 83ec4690b9..81bd8066a6 100644 --- a/index.bs +++ b/index.bs @@ -2726,7 +2726,7 @@ To create [=aggregatable contributions=] given an [=attribution sourc [=aggregatable trigger data/key piece=]. 1. Let |aggregatableValuesConfigurations| be |trigger|'s [=attribution trigger/aggregatable values configurations=]. 1. [=list/iterate|For each=] |aggregatableValuesConfiguration| of |aggregatableValuesConfigurations|: - 1. If the result of running [=match an attribution source's filter data against filters and negated filters=] with + 1. If the result of running [=match an attribution source against filters and negated filters=] with |source|, |aggregatableValuesConfiguration|'s [=aggregatable values configuration/filters=], |aggregatableValuesConfiguration|'s [=aggregatable values configuration/negated filters=], and |trigger|'s [=attribution trigger/trigger time=] is true: From bb358701089fe351d17d117be03f096e0a3cb658 Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Wed, 7 Feb 2024 15:59:52 +0000 Subject: [PATCH 16/21] Update explainer with new registration option --- AGGREGATE.md | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/AGGREGATE.md b/AGGREGATE.md index 46f2b423b5..426448e11f 100644 --- a/AGGREGATE.md +++ b/AGGREGATE.md @@ -172,9 +172,6 @@ The scheme above will generate the following abstract histogram contributions: value: 1664 }] ``` -Note: The `filters` field will still apply to aggregatable reports, and each -dict in `aggregatable_trigger_data` can still optionally have filters applied -to it just like for event-level reports. Note: the above scheme was used to maximize the [contribution budget](#contribution-bounding-and-budgeting) and optimize utility in the face @@ -185,6 +182,30 @@ true_agg_campaign_counts = raw_agg_campaign_counts / (L1 / 2) true_agg_geo_value = 1024 * raw_agg_geo_value / (L1 / 2) ``` +Note: The `filters` field will still apply to aggregatable reports, and each +dict in `aggregatable_trigger_data` can still optionally have filters applied +to it just like for event-level reports. + +Note: The `aggregatable_values` field may also be specified as a list of dict, +where each dict contains a dict of key-value pair as outlined above as well as +an optional `filters` (and its corresponding `not_filters`) field, allowing +trigger registrations to customize how values are contributed to keys depending +on the context of the source. +```jsonc +{ + ... + "aggregatable_values": [ + { + "values": { + "campaignCounts": 32768, + "geoValue": 1664 + }, + "filters": {"source_type": ["navigation"]} + } + ] +} +``` + Trigger registration will accept an optional field `aggregatable_deduplication_keys` which will be used to deduplicate multiple triggers containing the same `deduplication_key` for a single source with selective filtering. From 97fa37628181f8a912502b1df638cee58f23a86e Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Mon, 12 Feb 2024 15:50:47 +0000 Subject: [PATCH 17/21] renames, minor changes to explainer --- AGGREGATE.md | 9 +++++---- index.bs | 10 +++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/AGGREGATE.md b/AGGREGATE.md index 426448e11f..0f3417fe7a 100644 --- a/AGGREGATE.md +++ b/AGGREGATE.md @@ -188,12 +188,13 @@ to it just like for event-level reports. Note: The `aggregatable_values` field may also be specified as a list of dict, where each dict contains a dict of key-value pair as outlined above as well as -an optional `filters` (and its corresponding `not_filters`) field, allowing -trigger registrations to customize how values are contributed to keys depending -on the context of the source. +optional `filters` and `not_filters` fields, allowing trigger registrations to +customize how values are contributed to keys depending on source event data. If +multiple list entries have filters that match the source's filters, only the +first entry and its corresponding values will be used. ```jsonc { - ... + ..., "aggregatable_values": [ { "values": { diff --git a/index.bs b/index.bs index 81bd8066a6..6ce1ab42d8 100644 --- a/index.bs +++ b/index.bs @@ -1209,7 +1209,7 @@ controls the maximum [=map/size=] of an [=attribution source=]'s Max length per aggregation key identifier is a positive integer that controls the maximum [=string/length=] of an [=attribution source=]'s [=attribution source/aggregation keys=]'s -[=map/keys=], an [=attribution trigger=]'s [=attribution trigger/aggregatable values configurations=]'s item's +[=map/keys=], an [=attribution trigger=]'s [=attribution trigger/aggregatable values configurations=]'s [=list/item=]'s [=aggregatable values configuration/values=]'s [=map/keys=], and an [=aggregatable trigger data=]'s [=aggregatable trigger data/source keys=]'s [=set/items=]. Its value is 25. @@ -2427,7 +2427,7 @@ To parse aggregatable values given an [=ordered map=] |map|: 1. If |map|["`aggregatable_values`"] does not [=map/exist=], return a new [=list/is empty|empty=] [=list=]. 1. Let |values| be |map|["`aggregatable_values`"]. 1. If |values| is not an [=ordered map=] or a [=list=], return null. -1. Let |aggregatableValuesConfigurations| be a [=list=] of [=aggregatable values configuration=], initially empty. +1. Let |aggregatableValuesConfigurations| be a [=list=] of [=aggregatable values configurations=], initially empty. 1. If |values| is an [=ordered map=]: 1. Let |aggregatableKeyValues| be the result of running [=parse aggregatable key-values=] with |values|. 1. If |aggregatableKeyValues| is null, return null. @@ -2695,8 +2695,8 @@ Given an [=attribution trigger=] |trigger|, an [=attribution source=]

Creating aggregatable contributions

-To match [=aggregatable contributions=] to [=attribution source/aggregation keys=] given - an [=ordered map=] |aggregationKeys| and an [=ordered map=] |aggregatableValues|, +To create [=aggregatable contributions=] from [=attribution source/aggregation keys=] and aggregatable values + given an [=ordered map=] |aggregationKeys| and an [=ordered map=] |aggregatableValues|, run the following steps: 1. Let |contributions| be an empty [=list=]. @@ -2730,7 +2730,7 @@ To create [=aggregatable contributions=] given an [=attribution sourc |source|, |aggregatableValuesConfiguration|'s [=aggregatable values configuration/filters=], |aggregatableValuesConfiguration|'s [=aggregatable values configuration/negated filters=], and |trigger|'s [=attribution trigger/trigger time=] is true: - 1. Return the result of running [=match aggregatable contributions to aggregation keys=] with + 1. Return the result of running [=create aggregatable contributions from aggregation keys and aggregatable values=] with |aggregationKeys| and |aggregatableValuesConfiguration|'s [=aggregatable values configuration/values=]. 1. Return a new [=list/is empty|empty=] [=list=]. From dcc661c39987df3d0cc877c3bc3385c6a1deae5f Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Mon, 12 Feb 2024 16:07:56 +0000 Subject: [PATCH 18/21] update aggregatable data check --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 6ce1ab42d8..3ab3ee06d3 100644 --- a/index.bs +++ b/index.bs @@ -3078,7 +3078,7 @@ To check if an [=attribution trigger=] contains aggregatable data giv run the following steps: 1. If |trigger|'s [=attribution trigger/aggregatable trigger data=] is not [=list/is empty|empty=], return true. -1. If |trigger|'s [=attribution trigger/aggregatable values configurations=] is not [=list/is empty|empty=], return true. +1. If any of |trigger|'s [=attribution trigger/aggregatable values configurations=]'s [=aggregatable values configuration/values=] is not [=list/is empty|empty=], return true. 1. Return false. To trigger attribution given an [=attribution trigger=] |trigger|, run the following steps: From f4f3d4c4c7bb6ec180fcacf61983c1ea4cca4e41 Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Mon, 12 Feb 2024 16:17:33 +0000 Subject: [PATCH 19/21] removes ordered qualifier --- AGGREGATE.md | 15 +++++++++------ index.bs | 10 +++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/AGGREGATE.md b/AGGREGATE.md index 0f3417fe7a..2babeaecca 100644 --- a/AGGREGATE.md +++ b/AGGREGATE.md @@ -186,12 +186,15 @@ Note: The `filters` field will still apply to aggregatable reports, and each dict in `aggregatable_trigger_data` can still optionally have filters applied to it just like for event-level reports. -Note: The `aggregatable_values` field may also be specified as a list of dict, -where each dict contains a dict of key-value pair as outlined above as well as -optional `filters` and `not_filters` fields, allowing trigger registrations to -customize how values are contributed to keys depending on source event data. If -multiple list entries have filters that match the source's filters, only the -first entry and its corresponding values will be used. +Note: The `aggregatable_values` field may also be specified as a list of +dictionaries, where each dictionary contains a dictionary `values` of key-value +pairs as outlined above as well as optional `filters` and `not_filters` fields, +allowing trigger registrations to customize how values are contributed to keys +depending on source filter data. If multiple list entries have filters that +match the source's filters, only the first entry and its corresponding values +will be used. + +For example: ```jsonc { ..., diff --git a/index.bs b/index.bs index 3ab3ee06d3..93c5d30a0f 100644 --- a/index.bs +++ b/index.bs @@ -803,7 +803,7 @@ An aggregatable values configuration is a [=struct=] with the following items:
: values -:: An [=ordered map=] whose [=map/key|keys=] are [=strings=] and whose [=map/value|values=] are non-negative 32-bit integers. +:: A [=map=] whose [=map/key|keys=] are [=strings=] and whose [=map/value|values=] are non-negative 32-bit integers. : filters :: A [=list=] of [=filter configs=]. : negated filters @@ -2412,7 +2412,7 @@ To parse aggregatable trigger data given an [=ordered map=] |map|: 1. [=list/Append=] |aggregatableTrigger| to |aggregatableTriggerData|. 1. Return |aggregatableTriggerData|. -To parse aggregatable key-values given an [=ordered map=] |map|: +To parse aggregatable key-values given a [=map=] |map|: 1. [=map/iterate|For each=] |key| → |value| of |map|: 1. If |key|'s [=string/length=] is greater than the [=max length per aggregation key identifier=], @@ -2428,7 +2428,7 @@ To parse aggregatable values given an [=ordered map=] |map|: 1. Let |values| be |map|["`aggregatable_values`"]. 1. If |values| is not an [=ordered map=] or a [=list=], return null. 1. Let |aggregatableValuesConfigurations| be a [=list=] of [=aggregatable values configurations=], initially empty. -1. If |values| is an [=ordered map=]: +1. If |values| is a [=map=]: 1. Let |aggregatableKeyValues| be the result of running [=parse aggregatable key-values=] with |values|. 1. If |aggregatableKeyValues| is null, return null. 1. Let |aggregatableValuesConfiguration| be a new [=aggregatable values configuration=] with the items: @@ -2441,7 +2441,7 @@ To parse aggregatable values given an [=ordered map=] |map|: 1. [=list/Append=] |aggregatableValuesConfiguration| to |aggregatableValuesConfigurations|. 1. Return |aggregatableValuesConfigurations|. 1. [=list/iterate|For each=] |value| of |values|: - 1. If |value| is not an [=ordered map=], return null. + 1. If |value| is not a [=map=], return null. 1. If |value|["`values`"] does not [=map/exist=], return null. 1. Let |aggregatableKeyValues| be the result of running [=parse aggregatable key-values=] with |value|["`values`"]. 1. If |aggregatableKeyValues| is null, return null. @@ -2696,7 +2696,7 @@ Given an [=attribution trigger=] |trigger|, an [=attribution source=]

Creating aggregatable contributions

To create [=aggregatable contributions=] from [=attribution source/aggregation keys=] and aggregatable values - given an [=ordered map=] |aggregationKeys| and an [=ordered map=] |aggregatableValues|, + given an [=ordered map=] |aggregationKeys| and a [=map=] |aggregatableValues|, run the following steps: 1. Let |contributions| be an empty [=list=]. From 49ab7ffe4af15f5bbaeba4e61e15bd654a49a6e0 Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Mon, 12 Feb 2024 16:24:05 +0000 Subject: [PATCH 20/21] link values to definition --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 93c5d30a0f..bc47445721 100644 --- a/index.bs +++ b/index.bs @@ -2695,7 +2695,7 @@ Given an [=attribution trigger=] |trigger|, an [=attribution source=]

Creating aggregatable contributions

-To create [=aggregatable contributions=] from [=attribution source/aggregation keys=] and aggregatable values +To create [=aggregatable contributions=] from [=attribution source/aggregation keys=] and aggregatable [=aggregatable values configuration/values=] given an [=ordered map=] |aggregationKeys| and a [=map=] |aggregatableValues|, run the following steps: From f1ff59675ee9efa8005fa08d7117e9de86ce2147 Mon Sep 17 00:00:00 2001 From: Thomas Quintanilla Date: Mon, 12 Feb 2024 16:33:35 +0000 Subject: [PATCH 21/21] removes ordered qualifier --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index bc47445721..b07bc9940e 100644 --- a/index.bs +++ b/index.bs @@ -2696,7 +2696,7 @@ Given an [=attribution trigger=] |trigger|, an [=attribution source=]

Creating aggregatable contributions

To create [=aggregatable contributions=] from [=attribution source/aggregation keys=] and aggregatable [=aggregatable values configuration/values=] - given an [=ordered map=] |aggregationKeys| and a [=map=] |aggregatableValues|, + given a [=map=] |aggregationKeys| and a [=map=] |aggregatableValues|, run the following steps: 1. Let |contributions| be an empty [=list=].