diff --git a/index.bs b/index.bs
index 6c153f603..61ea82b46 100644
--- a/index.bs
+++ b/index.bs
@@ -1178,9 +1178,10 @@ controls the maximum [=map/size=] of a [=trigger spec map=] for an
controls how many [=attribution sources=] can be in the
[=attribution source cache=] per [=attribution source/source origin=].
-Randomized response epsilon is a non-negative double that controls
-the randomized response probability of an [=attribution source=]. If [=automation local testing mode=] is true,
-this is `∞`.
+Max settable event-level epsilon is a non-negative double that
+controls the default and maximum values that a source registration can specify
+for the epsilon parameter used by [=compute the channel capacity of a source=]
+and [=obtain a randomized source response=].
Randomized null report rate excluding source registration time is a
double between 0 and 1 (both inclusive) that controls the randomized number of null reports
@@ -1986,7 +1987,10 @@ To parse source-registration JSON given a [=byte sequence=]
: [=randomized response output configuration/trigger specs=]
:: |triggerSpecs|
-1. Let |epsilon| be the user agent's [=randomized response epsilon=].
+1. Let |epsilon| be the user agent's [=max settable event-level epsilon=].
+1. Set |epsilon| to |value|["`event_level_epsilon`"] if it [=map/exists=]:
+1. If |epsilon| is not a double, is less than 0, or is greater than the user agent's [=max settable event-level epsilon=], return null.
+1. If [=automation local testing mode=] is true, set |epsilon| to `∞`.
1. If the result of [=computing the channel capacity of a source=] with |randomizedResponseConfig| and |epsilon| is greater than
[=max event-level channel capacity per source=][|sourceType|], return null.
1. Let |triggerDataMatchingMode| be "[=trigger-data matching mode/modulus=]
".
@@ -3904,7 +3908,8 @@ Any given [=attribution source=] has a [=obtain a set of possible trigger states
The choice of trigger state may encode cross-site information. To protect the cross-site information disclosure,
each [=attribution source=] is subject to a [=obtain a randomized response|randomized response mechanism=] [[RR]],
which will choose a state at random with [=obtain a randomized source response pick rate|pick rate=]
-dependent on the user agent's [=randomized response epsilon=].
+dependent on the source's event-level epsilon, which has an upper bound of the
+user agent's [=max settable event-level epsilon=].
This introduces some level of plausible deniability into the resulting [=event-level reports=] (or lack thereof),
as there is always a chance that the output was generated from a random process. We can reason about the
diff --git a/params/chromium-params.md b/params/chromium-params.md
index 2f2891f17..984700bad 100644
--- a/params/chromium-params.md
+++ b/params/chromium-params.md
@@ -8,7 +8,7 @@ Chromium's implementation assigns the following values:
| Name | Value |
| ---- | ----- |
| [Max pending sources per source origin][] | [4096][max pending sources per source origin value] |
-| [Randomized response epsilon][] | [14][randomized response epsilon value] |
+| [Max settable event-level epsilon][] | [14][max settable event-level epsilon value] |
| [Randomized null report rate excluding source registration time][] | [0.05][randomized null report rate excluding source registration time value] |
| [Randomized null report rate including source registration time][] | [0.008][randomized null report rate including source registration time value] |
| [Max event-level reports per attribution destination][] | [1024][max event-level reports per attribution destination value] |
@@ -28,8 +28,8 @@ Chromium's implementation assigns the following values:
[Max pending sources per source origin]: https://wicg.github.io/attribution-reporting-api/#max-pending-sources-per-source-origin
[max pending sources per source origin value]: https://source.chromium.org/chromium/chromium/src/+/main:content/browser/attribution_reporting/attribution_config.h;l=151;drc=3be0e68c5ed56aba7c321cbaea22558eee61fc50
-[Randomized response epsilon]: https://wicg.github.io/attribution-reporting-api/#randomized-response-epsilon
-[randomized response epsilon value]: https://source.chromium.org/chromium/chromium/src/+/main:content/browser/attribution_reporting/attribution_config.h;l=57;drc=3733a639d724a4353463a872605119d11a1e4d37
+[Max settable event-level epsilon]: https://wicg.github.io/attribution-reporting-api/#max-settable-event-level-epsilon
+[max settable event-level epsilon value]: https://source.chromium.org/chromium/chromium/src/+/main:content/browser/attribution_reporting/attribution_config.h;l=57;drc=3733a639d724a4353463a872605119d11a1e4d37
[Randomized null report rate excluding source registration time]: https://wicg.github.io/attribution-reporting-api/#randomized-null-report-rate-excluding-source-registration-time
[randomized null report rate excluding source registration time value]: https://source.chromium.org/chromium/chromium/src/+/main:content/browser/attribution_reporting/attribution_config.h;l=109;drc=3733a639d724a4353463a872605119d11a1e4d37
[Randomized null report rate including source registration time]: https://wicg.github.io/attribution-reporting-api/#randomized-null-report-rate-including-source-registration-time
diff --git a/ts/src/header-validator/source.test.ts b/ts/src/header-validator/source.test.ts
index bfe4e9398..d7ebcb0b1 100644
--- a/ts/src/header-validator/source.test.ts
+++ b/ts/src/header-validator/source.test.ts
@@ -45,6 +45,7 @@ const testCases: TestCase[] = [
debugKey: 1n,
debugReporting: true,
destination: new Set(['https://a.test']),
+ eventLevelEpsilon: 14,
eventReportWindows: {
startTime: 0,
endTimes: [3601],
@@ -1186,7 +1187,7 @@ const testCases: TestCase[] = [
[SourceType.event]: 0,
[SourceType.navigation]: Infinity,
},
- randomizedResponseEpsilon: 14,
+ maxSettableEventLevelEpsilon: 14,
},
expectedErrors: [
{
@@ -1204,7 +1205,7 @@ const testCases: TestCase[] = [
[SourceType.event]: Infinity,
[SourceType.navigation]: 0,
},
- randomizedResponseEpsilon: 14,
+ maxSettableEventLevelEpsilon: 14,
},
expectedErrors: [
{
@@ -1214,6 +1215,53 @@ const testCases: TestCase[] = [
],
},
+ {
+ name: 'event-level-epsilon-valid',
+ json: `{
+ "destination": "https://a.test",
+ "event_level_epsilon": 13.5
+ }`,
+ },
+ {
+ name: 'event-level-epsilon-wrong-type',
+ json: `{
+ "destination": "https://a.test",
+ "event_level_epsilon": "1"
+ }`,
+ expectedErrors: [
+ {
+ path: ['event_level_epsilon'],
+ msg: 'must be a number',
+ },
+ ],
+ },
+ {
+ name: 'event-level-epsilon-negative',
+ json: `{
+ "destination": "https://a.test",
+ "event_level_epsilon": -1
+ }`,
+ expectedErrors: [
+ {
+ path: ['event_level_epsilon'],
+ msg: 'must be in the range [0, 14]',
+ },
+ ],
+ },
+ {
+ name: 'event-level-epsilon-above-max',
+ json: `{
+ "destination": "https://a.test",
+ "event_level_epsilon": 14.1
+ }`,
+ expectedErrors: [
+ {
+ path: ['event_level_epsilon'],
+ msg: 'must be in the range [0, 14]',
+ },
+ ],
+ },
+
// Full Flex
// TODO: compare returned trigger specs against expected values
diff --git a/ts/src/header-validator/validate-json.ts b/ts/src/header-validator/validate-json.ts
index cc088c971..7215acb2f 100644
--- a/ts/src/header-validator/validate-json.ts
+++ b/ts/src/header-validator/validate-json.ts
@@ -792,6 +792,12 @@ function eventReportWindow(
)
}
+function eventLevelEpsilon(ctx: Context, j: Json): Maybe {
+ return number(ctx, j).filter((n) =>
+ isInRange(ctx, n, 0, ctx.vsv.maxSettableEventLevelEpsilon)
+ )
+}
+
function channelCapacity(ctx: Context, s: Source): void {
const perTriggerDataConfigs = s.triggerSpecs.flatMap((spec) =>
Array(spec.triggerData.size).fill(
@@ -808,7 +814,7 @@ function channelCapacity(ctx: Context, s: Source): void {
)
const { infoGain } = config.computeConfigData(
- ctx.vsv.randomizedResponseEpsilon,
+ s.eventLevelEpsilon,
ctx.vsv.maxEventLevelChannelCapacityPerSource[ctx.sourceType]
)
@@ -1057,6 +1063,8 @@ export type Source = CommonDebug &
triggerSpecs: TriggerSpec[]
triggerDataMatching: TriggerDataMatching
+
+ eventLevelEpsilon: number
}
function source(ctx: Context, j: Json): Maybe {
@@ -1110,6 +1118,11 @@ function source(ctx: Context, j: Json): Maybe {
),
aggregationKeys: field('aggregation_keys', aggregationKeys, new Map()),
destination: field('destination', destination),
+ eventLevelEpsilon: field(
+ 'event_level_epsilon',
+ eventLevelEpsilon,
+ ctx.vsv.maxSettableEventLevelEpsilon
+ ),
eventReportWindows: () => eventReportWindowsVal,
expiry: () => expiryVal,
filterData: field('filter_data', filterData, new Map()),
diff --git a/ts/src/vendor-specific-values.ts b/ts/src/vendor-specific-values.ts
index 2cfaefe47..af9cdae64 100644
--- a/ts/src/vendor-specific-values.ts
+++ b/ts/src/vendor-specific-values.ts
@@ -2,7 +2,7 @@ import { SourceType } from './source-type'
export type VendorSpecificValues = {
maxEventLevelChannelCapacityPerSource: Record
- randomizedResponseEpsilon: number
+ maxSettableEventLevelEpsilon: number
}
export const Chromium: Readonly = {
@@ -10,5 +10,5 @@ export const Chromium: Readonly = {
[SourceType.event]: 6.5,
[SourceType.navigation]: 11.46173,
},
- randomizedResponseEpsilon: 14,
+ maxSettableEventLevelEpsilon: 14,
}