telemetry-aggregation §6.1's rule that `rejected` counts no report the aggregator aggregated had no test, and impression_aggregate_verify.py did not apply it. The flight flow now sends the helper a report it committed, and the Python flow filters as the Rust does. impression-aggregates §7.3 says what a reader does with a statement of version 1 beside one of version 2, and the corpus adds a receipt and a table of each. The ledger's corpus takes these receipts, so it is regenerated by the commit after this one. Co-Authored-By: Claude Opus 5.5 <[email protected]>
735 lines
48 KiB
Markdown
735 lines
48 KiB
Markdown
# WAI Extension: Impression Aggregates
|
||
|
||
> Status: Draft. Registers the record `wai.delivery.impressions` and measure 5, `impressions`, of
|
||
> [telemetry-aggregation](telemetry-aggregation.md) §3 (its §3.1): ad presentations counted over
|
||
> a bearer with no return path. A receiver judges each ad of a shared pod-input record it
|
||
> presented by [stitched-presentation](stitched-presentation.md)'s rules, and turns each
|
||
> presentation into one Prio3Histogram report of a task of that measure, whose `scope` is the
|
||
> digest of a **catalogue** (`WIC1`, §4) that fixes which index of the vector is which creative,
|
||
> region and quartile. It generates the report once, keeps its bytes, and sends them over
|
||
> whatever path it later has, in an **upload file** (`WIU1`, §6.4) that relays forward unchanged.
|
||
> It accepts a task only from a **task file** (`WIT1`, §5.4) whose signer its pinned key log
|
||
> lists with the authority `aggregate/task`, and with attestation each report carries a Privacy
|
||
> Pass token bound to its task and its report id, in a private report extension (§7.2). The
|
||
> leader holds each collection for the catalogue's `hold_s`, and the aggregators keep the task
|
||
> until its retention instant, so late reports still count (§7.1). The task layout, the task id,
|
||
> the aggregators, the statements (`WQS1`) and the receipt (`WQA1`) are telemetry-aggregation's;
|
||
> a reader verifies a receipt against its catalogue and recomputes a table of presentations and
|
||
> quartile counts per creative and region (§8): a lower bound of what receivers reported, never
|
||
> an audience, beside what each aggregator states it rejected (§7.3). The reference
|
||
> implementation is the `impression_aggregate` module of `wai-rs`
|
||
> (feature `impression_aggregate`); the corpus is
|
||
> [`live-conformance/impressions/`](../live-conformance/impressions/), and its second
|
||
> implementation is `live-conformance/impression_aggregate_verify.py`.
|
||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||
>
|
||
> Texts read for this revision: `draft-ietf-ppm-dap-20` (DAP) §2.3, §3.1, §3.6, §4.1, §4.1.1,
|
||
> §4.2, §4.2.2, §4.2.3, §4.4.1, §4.4.2, §4.4.2.1, §4.4.2.2, §4.4.3, §4.5.3.3, §4.5.3.4, §4.5.4.3,
|
||
> §4.6.1, §5.1, §6.1.1, §6.3, §6.4.1, §8.1 to §8.4, §8.6.4, §9.2, §9.2.4 and Appendix B.4;
|
||
> `draft-irtf-cfrg-vdaf-22` (VDAF) §7.4.3.1, §7.4.4, §7.4.5, §9.2, Table 4, Table 13 and §10
|
||
> Table 19; RFC 9576 §3.3; RFC 9577 §2.1.1, §2.1.1.2 and §2.2.1; RFC 9578 §6.4 and §6.5; and,
|
||
> informatively, `draft-ietf-privacypass-batched-tokens-10` §2, §3 and §7. ISO 3166-1 and
|
||
> ISO 3166-2 were not read; the code form is the one [rung-price](rung-price.md) §2 states. A
|
||
> clause number of one of them in this text is that text's.
|
||
|
||
## 1. Scope
|
||
|
||
In scope: the catalogue (§4); measure 5's task, its rules and the task file (§5); the receiver's
|
||
contribution, report, upload file, upload plan and settlement (§6); what the aggregators add to
|
||
DAP: retention, the hold, the token check and what each aggregator rejected (§7); the verification
|
||
of a receipt of an impression task, and the impression table (§8).
|
||
|
||
Out of scope: issuing tokens (RFC 9578, and the batched issuance of
|
||
`draft-ietf-privacypass-batched-tokens-10`); how a receiver learns its region or sets its clock;
|
||
running aggregators; choosing a helper's operator (DAP §8.7); differential privacy; prices and
|
||
invoices; an audience or a reach; and two-way paths, which keep
|
||
[stream-audit](stream-audit.md) and stitched-presentation.
|
||
|
||
Relationship to the core spec: `wai.delivery.impressions` is a **record** ([SPEC](../SPEC.md)
|
||
§5). It names no payload format and is not dispatchable.
|
||
|
||
Relationship to telemetry-aggregation: a task of measure 5 is one of its tasks. This text
|
||
supplies the measure's configuration and rules (§5.3), its reports (§6.3), what its aggregators
|
||
add (§7) and the layer a reader puts over its accepted receipts (§8). Every other rule of that
|
||
text stands.
|
||
|
||
## 2. Terms
|
||
|
||
- **DAP**, **task**, **report**, **leader**, **helper**, **collector**, **batch**, **batch
|
||
interval**, **measure**: as [telemetry-aggregation](telemetry-aggregation.md) §2.
|
||
- **Record**, **claim**, **marker**, **presented**: as stitched-presentation §2.
|
||
- **Creative digest**: as [ad-presentation](ad-presentation.md) §4. **Token**: as
|
||
ad-presentation §2.
|
||
- **Key log**, **anchor**, **pinned**, **confirmed**: as [jwp-receipts](jwp-receipts.md) §7.
|
||
- **Region code**, **holds**: as rung-price §2.
|
||
- **Instant**: a count of milliseconds since 1970-01-01T00:00:00Z, leap seconds not counted, as
|
||
[carousel](carousel.md) §2 and [holdings](holdings.md) §2.
|
||
- **Receiver**: the sink that presents the ads of a bearer and reports them.
|
||
- **Presentation**: a claim the receiver's player signs that is presented under §6.2.
|
||
- **Flight**: a task's `task_interval` (DAP §4.2.3).
|
||
- **Retention instant**: §5.3 step 3.
|
||
- **Relay**: any node that carries an upload file toward the leader.
|
||
|
||
## 3. What one-way delivery changes
|
||
|
||
Telemetry-aggregation §5 (its §5.1) has a sink report when its session ends, over a path it has
|
||
then, and DAP §6.1.1 says that "Clients are not expected to store any state across any upload
|
||
flows, nor are they required to implement any sort of report upload retry mechanism". On a
|
||
bearer with no return path:
|
||
|
||
- **Late reports.** A receiver reports hours or days after it presented. DAP discards a report
|
||
of a batch already collected (§4.4.2.2), and aggregators may delete a task after its
|
||
`task_interval` (§6.4.1). So the catalogue states a hold; the leader collects no batch before
|
||
its end plus the hold, and the aggregators keep the task until the retention instant (§7.1);
|
||
a receiver sends the reports nearest their deadline first (§6.5). A report later than its
|
||
batch's collection is lost: a count is lower, never higher.
|
||
- **Duplicates.** A receiver that crashed, kept two copies of its state, or sent by two relays
|
||
could count one presentation twice. So a report is generated once, stored, and re-sent as the
|
||
same bytes until it is settled (§6.3, §6.6), and DAP's report-id check discards every later
|
||
copy. A playback session gives one report for a record and an ad (stitched-presentation §7.3
|
||
step 7). A report is never regenerated: its id is VDAF §9.2's nonce, and a second id would be
|
||
a second count.
|
||
- **Replay.** A captured upload replayed to the same task is discarded by its ids. Replayed to
|
||
another task, its shares do not decrypt, since their authenticated data holds the task. A
|
||
token answers one report id of one task (§7.2) and travels sealed.
|
||
- **A receiver that never returns.** Its presentations are never counted. A table is a lower
|
||
bound on reported presentations, never an audience, and never the *n* of
|
||
[bearer-receipts](bearer-receipts.md) §7 (§8.3).
|
||
- **No path at play time.** No challenge reaches a receiver before an ad plays, so a token
|
||
cannot be bound to the presentation as ad-presentation §7.2 binds one; it is bound to a report
|
||
id drawn ahead (§6.1, §7.2). No HPKE configuration can be fetched (DAP §4.4.1), so the task
|
||
file carries them (§5.4). No task is trusted because it arrived on the bearer, so its signer
|
||
must be confirmed under a pinned anchor (§5.4; [unidirectional-delivery](unidirectional-delivery.md)
|
||
§3).
|
||
|
||
## 4. The catalogue
|
||
|
||
### 4.1 Layout
|
||
|
||
A catalogue, `WIC1`, fixes the meaning of a task's vector. Integers are big-endian, except
|
||
inside `terms`, which is copied byte for byte from a pod-input record and is little-endian there
|
||
(stitched-presentation §4). A *short* field is a u8 length and that many bytes.
|
||
|
||
| size | field | meaning |
|
||
|---|---|---|
|
||
| 4 | `magic` | `WIC1` (ASCII) |
|
||
| 1 | `version` | 1 |
|
||
| 4 + long | `terms` | the measurement terms exactly as a pod-input record carries them (stitched-presentation §4.1): `presentation` (1), `viewability` (1), `token_type` (u16), `issuer` (a u16 length, then its bytes); ad-presentation §8's fields |
|
||
| 4 | `hold_s` | u32: the seconds after a batch interval ends before the leader completes a collection of it (§7.1) |
|
||
| 1 | `s` | the signers' count |
|
||
| 32 × `s` | `signers` | Ed25519 keys whose shared pod-input records count, strictly ascending |
|
||
| 1 | `c` | the creatives' count |
|
||
| 32 × `c` | `creatives` | creative digests (ad-presentation §4), strictly ascending |
|
||
| 1 | `r` | the named regions' count |
|
||
| short × `r` | `regions` | region codes, strictly ascending by bytes |
|
||
| 1 | `k` | the issuer keys' count |
|
||
| (2 + n) × `k` | `keys` | each a u16 length, then the DER SubjectPublicKeyInfo of a type-0x0002 issuer key (ad-presentation §7.3), strictly ascending by bytes |
|
||
|
||
The catalogue's **digest** is SHA-256 over all its bytes. It carries no signature: a task binds
|
||
it as its `scope` (§5.1), which enters every input share, every aggregate share and the
|
||
collector-signed receipt, and a task file signs it (§5.4).
|
||
|
||
### 4.2 Regions and cells
|
||
|
||
- A **code** is a region code of rung-price §2: an ISO 3166-1 alpha-2 code, or an ISO 3166-2
|
||
code, in capitals.
|
||
- Column *j*, for 0 ≤ *j* < `r`, is `regions[j]`; column `r` is **`other`**.
|
||
- A receiver's region *g*, a byte string or none, has the column of the listed code that holds
|
||
*g* (rung-price §2 *Holds*: the code itself, or the country of which *g* is a subdivision),
|
||
and otherwise `other`. Rule 6 of §4.3 leaves at most one such code. A *g* that is none, or is
|
||
not a code, has column `other`. Relations among ISO 3166-2 subdivisions are not followed: no
|
||
code shows its parent, so a receiver in a subdivision of a listed subdivision is `other`
|
||
unless its declared code is the listed one. A deployment lists codes at the granularity its
|
||
receivers declare.
|
||
|
||
With creative index *i* (the catalogue's order), column *j* and quartile *q* (1 to 5,
|
||
stitched-presentation §7.4), the **cell** is (*i* · (`r` + 1) + *j*) · 5 + (*q* − 1), and the
|
||
vector's length is *L* = `c` · (`r` + 1) · 5. The **chunk length** is ⌈√*L*⌉, the least integer
|
||
whose square is at least *L*: VDAF §7.4.4 recommends a chunk length "near the square root of
|
||
length", and §7.4.3.1 says the square root gives proofs "up to 50% larger than the optimal". It
|
||
fixes the configuration as one value.
|
||
|
||
### 4.3 Reading and rules
|
||
|
||
A reader reads exactly the given bytes, at most 1,048,576 of them. It refuses them
|
||
`catalogue_malformed` when they end before a field, go on past the last key, or when:
|
||
|
||
1. `magic` is not `WIC1`, or `version` is not 1;
|
||
2. `terms` breaks ad-presentation §8, does not set bit 0 (**claim**), or sets **attest** with a
|
||
`token_type` other than 2;
|
||
3. `hold_s` is 0: a collection at a bucket's end loses every report of a receiver that had no
|
||
path at that moment;
|
||
4. `s` is 0 or above 16, or `signers` is not strictly ascending;
|
||
5. `c` is 0, or `creatives` is not strictly ascending;
|
||
6. a region is not a code; `regions` is not strictly ascending; or a listed two-byte code *x*
|
||
has a listed code beginning *x*`-` (rung-price §4.5 rule 3's rule, for one footprint);
|
||
7. *L* is above 4,096. *L* is a multiple of 5, so the largest is 4,095, and a leader's
|
||
measurement share is then at most 65,520 bytes (Field128: VDAF Table 4 and Table 13);
|
||
8. `k` is not 0 when **attest** is clear, or not 1 to 4 when it is set; a key is not an
|
||
id-RSASSA-PSS SubjectPublicKeyInfo with a 2,048-bit modulus (ad-presentation §7.3); or
|
||
`keys` is not strictly ascending by bytes.
|
||
|
||
Each catalogue has exactly one encoding. The size bound decides no outcome: a catalogue that meets
|
||
these rules is under 340,000 bytes, its only long fields being the issuer and at most four keys,
|
||
each of a u16 length, so a longer buffer is refused whatever the bound. It lets a reader refuse
|
||
one without reading it.
|
||
|
||
## 5. The task
|
||
|
||
### 5.1 Measure 5 and its scope
|
||
|
||
A task of telemetry-aggregation's measure **5, `impressions`**, has that text's `task_info`
|
||
(telemetry-aggregation §4.1: 153 bytes, label `wai-qoe1`) with `measure` 5 and `scope` the
|
||
digest of its **catalogue**. Its VDAF is Prio3Histogram (VDAF §7.4.4, identifier 0x00000004 in
|
||
§10 Table 19) of the catalogue's *L* and chunk length, two aggregators. Its measurement is one
|
||
presentation's cell (§6.2).
|
||
|
||
### 5.2 Task id and verification key
|
||
|
||
Its task id is telemetry-aggregation §4.2's, and its verification key §4.4's. Two catalogues give
|
||
two scopes, two TaskConfigurations and two ids.
|
||
|
||
### 5.3 Rules
|
||
|
||
Given a catalogue *C* that reads (§4.3), a TaskConfiguration is an impression task of *C* unless,
|
||
in order:
|
||
|
||
1. `task_invalid`: telemetry-aggregation §4.3 rule 1 refuses it;
|
||
2. `catalogue_mismatch`: its `measure` is not 5, or its `scope` is not *C*'s digest;
|
||
3. `task_invalid` when its `time_precision` is below 3,600; its extensions are not exactly one
|
||
`task_interval` (DAP §4.2.3) holding an Interval of a duration of at least 1; or its
|
||
**retention instant**, in seconds, (`task_interval.start` + `task_interval.duration`) ·
|
||
`time_precision` + `hold_s`, overflows a u64 or is above the last instant in seconds,
|
||
253,402,300,799 (carousel §4.2; [timeliness](timeliness.md) §2);
|
||
4. `measure_mismatch`: `vdaf_type` is not 0x00000004, or `vdaf_configuration` is not
|
||
Prio3HistogramConfig {`length` *L*, `chunk_length` ⌈√*L*⌉} (DAP Appendix B.4);
|
||
5. `floor_too_low`: `min_batch_size` is below 50;
|
||
6. `aggregator_alone`: `leader` is `helper`.
|
||
|
||
These are telemetry-aggregation §4.3 for a task of measure 5: its rule 2 cannot fail, since 5 is
|
||
a code of its §3, and its rule 3 is steps 2 to 4 here. The codes of steps 1 and 3 to 6 are that
|
||
text's, with its meaning.
|
||
|
||
### 5.4 The task file
|
||
|
||
A task file, `WIT1`, is how a task reaches receivers. Integers are big-endian.
|
||
|
||
| size | field | meaning |
|
||
|---|---|---|
|
||
| 4 | `magic` | `WIT1` (ASCII) |
|
||
| 1 | `version` | 1 |
|
||
| 2 + n | `task` | a u16 length, then the TaskConfiguration (DAP §4.2) |
|
||
| 4 + n | `catalogue` | a u32 length, then the catalogue |
|
||
| n | `leader_hpke` | the leader's HpkeConfigList as DAP §4.4.1 encodes it, its own u16 length first |
|
||
| n | `helper_hpke` | the helper's |
|
||
| 32 | `signer` | an Ed25519 key |
|
||
| 2 + n | `signer_id` | a u16 length, then the signer's identity string (jwp-receipts §7.1), ASCII |
|
||
| 64 | `sig` | Ed25519 by `signer` over `wai:impression-task` (ASCII), 0x01, then every byte before `sig`, verified strictly (SPEC §7 *Signatures*) |
|
||
|
||
The task file's **digest** is SHA-256 over the bytes before `sig`. Its signed bytes include
|
||
`signer` and `signer_id`, so it is a receipt of another WAI family that jwp-receipts §7.4
|
||
confirms.
|
||
|
||
A receiver holds key-log revisions and anchor records as bearer-receipts §8 (its §8.3) says:
|
||
provisioned logs pinned, and logs that arrive on the bearer held. It **accepts** a task file
|
||
unless, in order:
|
||
|
||
1. `task_file_malformed`: the file exceeds 1,048,576 bytes, has another magic or version, ends
|
||
early or has bytes after `sig`, or its `signer_id` is empty or not ASCII; or an HpkeConfigList
|
||
does not read (DAP §4.4.1: `HpkeConfig HpkeConfigList<10..2^16-1>`), holds two configurations
|
||
of one `id` (DAP §4.4.1: "Aggregators MUST allocate distinct id values"), or holds none of
|
||
KEM 0x0020, KDF 0x0001 and AEAD 0x0001 with a 32-byte key (DAP §7; telemetry-aggregation §5);
|
||
2. `catalogue_malformed`: the catalogue does not read (§4.3);
|
||
3. the task fails §5.3 against that catalogue, with that step's reason;
|
||
4. `bad_key`, `bad_signature`: the signature does not verify (SPEC §7);
|
||
5. the signer is not **confirmed** under jwp-receipts §7.4 with the authority `aggregate/task`,
|
||
which is pinned-only (jwp-receipts §7.5), the item untimed: the reason of the step that
|
||
fails, one of jwp-receipts §7.7's (`signer_id_not_origin_bound`,
|
||
`signer_id_names_another_key`, `origin_not_anchored`, a key-log reason of step 3,
|
||
`key_not_listed`, `key_mismatch`, `key_revoked`, `authority_not_granted`,
|
||
`anchor_not_pinned`, `origin_not_expected`). Unconfirmed and refused are both not accepted;
|
||
the reason says which.
|
||
|
||
A file that reaches step 5 has been read, checked and signature-verified; only the trust
|
||
decision remains. A revocation of the task key in a revision that arrived on the bearer reaches
|
||
a receiver whose anchor that revision descends from (bearer-receipts §8.2), and refuses the
|
||
file `key_revoked`. An untimed item passes a retired key (jwp-receipts §7.4 step 5): an origin
|
||
stops a task key by revoking it.
|
||
|
||
## 6. The receiver
|
||
|
||
### 6.1 What a receiver holds
|
||
|
||
- The task files it accepted, and the key logs it confirmed them under.
|
||
- Its region and its clock, as its own declared state ([unidirectional-delivery](unidirectional-delivery.md)
|
||
§2).
|
||
- For a catalogue that sets **attest**, a **pool** of pairs (report id, token) for the task.
|
||
Each id comes from a cryptographically secure random number generator (VDAF §9.2; DAP
|
||
§4.4.2). Each token is a type-0x0002 token for §7.2's TokenChallenge of that id, obtained while
|
||
the receiver had a path, by RFC 9578 issuance or by batched issuance. A receiver **SHOULD**
|
||
refill its pool to a fixed size rather than by what it used since the last refill, so issuance
|
||
shows the issuer a rate and not a count of presentations.
|
||
|
||
Evaluating anything here **MUST NOT** cause a request (unidirectional-delivery §2). A receiver
|
||
sends only §6.4's upload files, and token requests, on a path it already has.
|
||
|
||
### 6.2 Contribution
|
||
|
||
**Inputs:** an accepted task file; the pod-input record, by its bytes; the manifests the player
|
||
read; the creatives it holds; `claims`, the claims of this playback session already judged and
|
||
then the one judged now, each signed by the session key (stitched-presentation §5.2); `region`,
|
||
bytes or none; `at_ms`, the instant the presentation ended by the receiver's clock, or none when
|
||
the clock is not set; and `pool`, the unused pairs of an attested catalogue.
|
||
|
||
**Output:** **report**, with the cell, the creative index, the column, the quartile, the time
|
||
⌊`at_ms` / (1000 · `time_precision`)⌋, the report id (the chosen pair's, or a fresh one without
|
||
attestation) and the pool's ids discarded; or **no report**, with the reason of the first step
|
||
that fails, and the ids discarded:
|
||
|
||
1. The record, under stitched-presentation §7.2 steps 1 to 3 with the catalogue's `signers`
|
||
as the keys it accepts. Its response is not checked, as that section's step 5 allows, and no
|
||
price is used. Reasons: `pod_input_malformed`, `bad_key`, `bad_signature`,
|
||
`pod_signer_unknown`.
|
||
2. `record_not_shared`: the record's `session` is not 16 zero bytes. A record of one session
|
||
needs the session key to reach its signer (stitched-presentation §4.6), which a receiver with
|
||
no path cannot do.
|
||
3. `record_terms_differ`: the record's measurement-term bytes are not the catalogue's `terms`.
|
||
Every count of a table means one rule.
|
||
4. The claim judged now, under stitched-presentation §7.3 **steps 1 to 10**, over `claims` in
|
||
order, against that record: its reason (`stitched_claim_malformed`, `bad_key`,
|
||
`bad_signature`, `claim_key_unbound`, `claim_no_decision`, `claim_manifest_unknown`,
|
||
`claim_marker_absent`, `claim_duplicate`, `claim_asset_mismatch`, `claim_frame_mismatch`,
|
||
`claim_not_viewable`). Step 11 is not run: with **attest**, the report's token takes its
|
||
place (§7.2). `claim_duplicate` here is one report for a record, an ad and a session.
|
||
5. `creative_not_listed`: the ad's creative digest is not among `creatives`.
|
||
6. `instant_unknown`: `at_ms` is none. The upload time is never used in its place.
|
||
7. `outside_task`: the time is not within `task_interval`, which is half-open (DAP §4.1.1).
|
||
8. `token_exhausted`: the catalogue sets **attest** and no pair of `pool` passes §7.2 for this
|
||
task. Pairs are tried in ascending order of report id; a pair whose token fails is discarded
|
||
and listed.
|
||
|
||
Otherwise the quartile *q* is stitched-presentation §7.4's for the claim, the column is §4.2's
|
||
for `region`, and the cell is §4.2's.
|
||
|
||
### 6.3 The report
|
||
|
||
For a contribution that gives a report, the receiver generates **one** report as soon as the
|
||
presentation ends:
|
||
|
||
- **Report id**: the chosen pair's with **attest**; otherwise 16 bytes from a cryptographically
|
||
secure random number generator.
|
||
- **Time**: the contribution's.
|
||
- **Measurement**: the one-hot vector of length *L* at the cell.
|
||
- **Sharding and sealing**: as DAP §4.4.2.1 and telemetry-aggregation §5 (its §5.1), with the
|
||
application context `dap-20` followed by the task id; each input share sealed to the
|
||
**first** configuration of its aggregator's list in the task file with KEM 0x0020, KDF
|
||
0x0001 and AEAD 0x0001, since DAP §4.4.1 lists configurations "in decreasing order of
|
||
preference".
|
||
- **Extensions**: no public extension. With **attest**, the private extension
|
||
`wai_impression_token` (§7.2) in **both** input shares, its data the pair's token in
|
||
ad-presentation §7.1's form (354 bytes). Without it, no private extension.
|
||
- **Persistence**: the receiver stores the report's bytes under the presentation's key (the
|
||
record's digest, the ad's slot and the session), atomically, before anything is sent. It
|
||
**MUST NOT** generate a second report for a presentation that has a stored one, and **MUST
|
||
NOT** regenerate a report. VDAF §9.2: the nonce must come from a cryptographically secure
|
||
random number generator, and "Uniqueness of the nonce is not sufficient"; a regenerated report
|
||
has a new id while an earlier copy may still reach the leader by another path.
|
||
|
||
This departs from DAP §6.1.1 in what it expects of a client, not in anything DAP forbids. DAP
|
||
§4.4.2.2's "When the Client receives an hpke_unknown_config_id error, it SHOULD invalidate any
|
||
cached HpkeConfigList and retry with a freshly generated Report" is not followed: that is the
|
||
regeneration this section forbids (§6.6).
|
||
|
||
### 6.4 The upload file
|
||
|
||
| size | field | meaning |
|
||
|---|---|---|
|
||
| 4 | `magic` | `WIU1` (ASCII) |
|
||
| 1 | `version` | 1 |
|
||
| 32 | `task_id` | the task the reports were made for |
|
||
| 4 + n | `upload` | a u32 length, then a DAP UploadRequest: one or more Reports (DAP §4.4.2), in strictly ascending order of report id |
|
||
|
||
A reader reads exactly the given bytes, at most 16,777,216 of them, and refuses them
|
||
`upload_malformed` when they end early or go on past `upload`, the magic or the version is
|
||
wrong, `upload` holds no report, a report does not read as a DAP Report, or the ids are not
|
||
strictly ascending.
|
||
|
||
- The file is unsigned: DAP reports are anonymous, and each input share's authenticated data
|
||
binds its task.
|
||
- It travels as bearer-receipts §9 (its §9.1) sends a reconciliation: when the receiver has a
|
||
path; and where a bearer's own reporting carries files or opaque payloads, it **MAY** travel
|
||
there, for a gateway to forward.
|
||
- A relay forwards `upload` unchanged as the body of a POST to `{leader}/tasks/{task-id}/reports`
|
||
(DAP §4.4.2), the leader being the `leader_aggregator_endpoint` of the TaskConfiguration of the
|
||
task file of `task_id`, which the relay holds: the task file rode the bearer. It **MAY** do so
|
||
over Oblivious HTTP (RFC 9458; DAP §8.4).
|
||
- A relay **MAY** merge files of one `task_id` into one, ascending by id. Two reports of one id
|
||
with equal bytes are one; with different bytes, each is forwarded in a separate file and the
|
||
leader keeps the first. A relay that merged gives each source file the entries of the response
|
||
whose ids are its own, in their order. Merging hides from the leader which reports came from
|
||
one receiver.
|
||
|
||
### 6.5 The upload plan
|
||
|
||
**Inputs:** an accepted task file, the stored reports (bytes), an instant *t* (ms) and a budget
|
||
*b* (bytes, or none for no limit).
|
||
|
||
For a report of time τ, in this order: it is **dropped** when τ is outside `task_interval`, or
|
||
when *t* is at or past the retention instant · 1000, since the aggregators may then have deleted
|
||
the task (DAP §6.4.1). Otherwise its **deadline** is ((τ + 1) · `time_precision` + `hold_s`) ·
|
||
1000, which cannot overflow since τ is within the flight: the earliest instant the hold allows
|
||
any batch holding τ to be collected. It is **timely** when *t* is before its deadline, and
|
||
**late** otherwise; a late report may still count.
|
||
|
||
**Order:** timely before late; within each, ascending deadline, then ascending id. **Budget:**
|
||
reports are taken in that order while 41 plus the sum of their lengths stays within *b*; the first
|
||
that does not fit ends the taking. One task's reports are all of one length (one *L*, one set of
|
||
extensions), so nothing smaller follows.
|
||
|
||
**Outputs:** `send` (ids in order, each with its class), `defer` (ids), `drop` (ids), and `file`:
|
||
the `WIU1` of `send` in ascending order of id, or none when `send` is empty.
|
||
|
||
### 6.6 Settlement
|
||
|
||
**Inputs:** a `WIU1` the receiver sent, and the response body.
|
||
|
||
- The response reads as DAP's UploadErrors (DAP §4.4.2.2: ReportUploadStatus entries of a
|
||
16-byte id and a 1-byte error, to the message's end, "in the same order as in the request")
|
||
whose ids are a subsequence of the file's. Otherwise it **settles nothing**, and every report
|
||
is kept.
|
||
- A report with no entry was accepted: **delete** it.
|
||
- `report_too_early` (8): **keep** it; DAP §4.4.2.2: "the Client MAY re-upload the report later
|
||
on".
|
||
- Any other report error of DAP §4.1 (1 to 7, 9 and 10): **delete** it. That includes
|
||
`report_replayed` (2), which a leader **MAY** set for a report of a batch already collected
|
||
(DAP §4.4.2.2), and `hpke_unknown_config_id` (4), whose retry would be a regeneration: the
|
||
report is lost, an undercount.
|
||
- A value DAP §4.1 does not define: **keep** it. A re-send never counts twice (the report id), so
|
||
keeping costs bandwidth only, and an unknown value never reads as final.
|
||
|
||
An upload with no response keeps every report. A pending report is dropped by the plan at the
|
||
retention instant. **Outputs:** `delete` and `keep`, each a list of ids.
|
||
|
||
## 7. The aggregators
|
||
|
||
The aggregators and the collector run telemetry-aggregation §5, §6 and §7 as written for a task
|
||
of measure 5, with these additions.
|
||
|
||
### 7.1 Retention and the hold
|
||
|
||
**Retention.** An aggregator keeps the task, its batch buckets, its replay state and the HPKE
|
||
configurations the task file names until the task's retention instant (§5.3), and accepts
|
||
reports sealed to those configurations until then. DAP §6.4.1 lets it delete a task's data after
|
||
its `task_interval`, and §4.4.1 asks it to "continue to accept reports with old keys"; this sets
|
||
both bounds for a client that holds no newer configuration.
|
||
|
||
**The hold.** The leader **MUST NOT** finish a collection job until its clock is at or past its
|
||
batch interval's end in seconds, (`start` + `duration`) · `time_precision`, plus `hold_s`; until
|
||
then the job is processing (DAP §3.1, §4.6.1).
|
||
|
||
- A report uploaded after its batch is collected is discarded: the leader **MAY** mark it
|
||
`report_replayed` at upload (DAP §4.4.2.2), and one that reaches aggregation is
|
||
`batch_collected` (§4.5.4.3). It is lost.
|
||
- An early collection is the collector's undercount. No reader can see when a collection was
|
||
made, and nothing here claims otherwise.
|
||
|
||
### 7.2 Report extensions and the token
|
||
|
||
At DAP §4.5.3.4, after its input share decrypts, an aggregator refuses a report:
|
||
|
||
- with any **public** extension, `invalid_message`;
|
||
- of a catalogue without **attest**, with any private extension, `invalid_message` (a type the
|
||
task does not use: §4.5.3.4 step 4);
|
||
- of a catalogue with **attest**, unless its private extensions are exactly one
|
||
`wai_impression_token` (0x5749), recorded as `attestation_absent` (`invalid_message` on the
|
||
wire); and unless its data reads as a token of type 0x0002 (ad-presentation §7.1), has a
|
||
`challenge_digest` that is the SHA-256 of the TokenChallenge {0x0002, the catalogue's issuer,
|
||
the redemption context *R*, an empty origin_info} in ad-presentation §7.2's form, with *R* the
|
||
SHA-256 of `wai:impression-report-attest` (ASCII), 0x01, the task id and the report id; has a
|
||
`token_key_id` that is the SHA-256 of one of the catalogue's `keys`; and verifies under that
|
||
key (ad-presentation §7.3: RSASSA-PSS with SHA-384 and a 48-byte salt), recorded as
|
||
`attestation_invalid` (`invalid_message` on the wire).
|
||
|
||
Each aggregator checks its own input share, so a token in one share alone is refused by the
|
||
other. `attestation_absent` and `attestation_invalid` are ad-presentation §11's codes. Double
|
||
spending is the report-id replay check (DAP §2.3): RFC 9577 §2.1.1.2 says that for a non-empty
|
||
redemption context the double-spend state "only needs to be stored across the set of Origin
|
||
servers that will accept tokens with that redemption context", and *R* names one task and one
|
||
report.
|
||
|
||
**The codepoint.** DAP §9.2.4 sets aside no range for unregistered use, and its registries are
|
||
Specification Required (DAP §9.2). `wai_impression_token`, 0x5749, is proposed, with this text as
|
||
its specification, and is not registered. An aggregator that does not know it refuses every
|
||
attested report (DAP §4.5.3.4 step 4): no count, never a false one.
|
||
|
||
### 7.3 What each aggregator rejected
|
||
|
||
An aggregator of a task of measure 5 signs its statement in version 2, stating `rejected`
|
||
(telemetry-aggregation §6.1): the reports of the batch it rejected at aggregation, so a reader
|
||
sees beside the count what did not count. Reports with no token, with one of another report's id,
|
||
with one that does not verify, and with a measurement that does not check are among them. A
|
||
report the leader refuses never reaches the helper, and a token in one input share alone is
|
||
refused by the aggregator that lacks it (§7.2), so the two counts can differ, each aggregator
|
||
stating its own. Neither is held to the other, and neither is a count of presentations: a rejected report
|
||
says what an aggregator refused, never what a receiver saw.
|
||
|
||
A reader accepts a statement of version 1 of such a task, an earlier aggregator's, beside a statement
|
||
of either version. It states no count, which a reader reports as `null` for that aggregator alone,
|
||
never as 0: the other aggregator's count stands beside it (§8.1, §8.2).
|
||
|
||
## 8. Verification
|
||
|
||
### 8.1 A receipt of an impression task
|
||
|
||
**Inputs:** the receipt, the catalogue, and optionally the batch's reports (each report's id,
|
||
time and task).
|
||
|
||
**Steps**, stopping at the first refusal, which is stated with its reason and place: the
|
||
catalogue reads (§4.3; `catalogue_malformed`, at `catalogue`). Then telemetry-aggregation §8.1
|
||
steps 1 to 11 as written, its step 2 being §5.3 here against the catalogue. Then:
|
||
|
||
12. at `leader`: the statements' batch falls within the `task_interval` (DAP §4.1.1), otherwise
|
||
`batch_invalid` (telemetry-aggregation's code: a batch the task does not admit);
|
||
13. at `result`: each element of the result is at most the report count, and the elements' sum,
|
||
as integers, is the report count, otherwise `count_mismatch`. With *L* at most 4,095 and each
|
||
element at most a u64 count, the sum fits in a u128.
|
||
|
||
**Outcome:** accepted, stating telemetry-aggregation §8.2's fields (measure `impressions`, scope
|
||
the catalogue's digest), **`rejected`**, each aggregator's count or `null` for a statement of
|
||
version 1, which states none, and **`reports_checked`**, whether step 11 ran; or the first refusal.
|
||
|
||
### 8.2 The table
|
||
|
||
**Inputs:** the catalogue, the TaskConfiguration, and receipts in an order, each with its reports
|
||
where they are given.
|
||
|
||
1. The catalogue reads (`catalogue_malformed`) and the task meets §5.3 against it; otherwise the
|
||
**table** is refused with that reason.
|
||
2. Each receipt, in order, is verified by §8.1, and its `task` must then be the given
|
||
TaskConfiguration byte for byte (`task_mismatch` at `task`, telemetry-aggregation's code). A
|
||
refused receipt is stated with its reason and place and takes no further part.
|
||
3. Of the accepted receipts, those with equal digests are one. Two whose batch intervals share a
|
||
time unit are **both** set aside, `receipts_overlap`: DAP collects a bucket once (DAP §2.3;
|
||
`batchOverlap`), so an honest pair cannot exist, and a reader holds no ground to choose
|
||
between them (carousel §6.2 step 4, holdings §5.3). With the receipts sorted by batch
|
||
start, a receipt overlaps an earlier one when the greatest end before it is past its start,
|
||
and a later one when the next start is before its end.
|
||
4. Each receipt left **stands**.
|
||
|
||
**Outcome:**
|
||
|
||
- for each receipt, in order: its digest where it reads, and its verdict (`stands`,
|
||
`set-aside reason=…` or `refuse reason=… at=…`); for a standing one, its batch (`start` and
|
||
`duration` in `time_precision` units, `from_ms` and `to_ms`), `collectable_from_ms` =
|
||
((`start` + `duration`) · `time_precision` + `hold_s`) · 1000, the earliest instant the hold
|
||
allows its collection, `report_count`, `rejected`, each aggregator's, `reports_checked`, and its
|
||
**rows**;
|
||
- **totals**: the rows summed over the standing receipts, each counted once, with the list of
|
||
their batch intervals, each aggregator's `rejected` summed over them, `null` for an aggregator
|
||
when one of them states none, and whether every one's reports were checked. A receipt given more than
|
||
once is checked when any of its occurrences was: they are the same bytes, and §8.1 accepted the
|
||
one given with its reports, so the order in which they arrive changes nothing. Each total is the
|
||
exact integer sum. The standing batches share no time unit, so there are fewer than 2^27 of
|
||
them (§5.3 bounds the retention instant), and each element is at most a u64 count (§8.1 step
|
||
13): a total is below 2^91 and fits in a u128, and a reader **MUST NOT** wrap or saturate it at
|
||
2^64. A `rejected` total is bounded the same way. When none stands, the totals are empty: no
|
||
row, never a row of zeros, and each `rejected` `null`.
|
||
|
||
A **row** is a (creative index, column) pair, for every creative and column in the catalogue's
|
||
order, `other` last. It states `presentations`, the sum of its five cells; `quartiles`, five
|
||
counts, those at quartile at least 1, 2, 3, 4 and 5, as stitched-presentation §7.4 states them;
|
||
and `floor_met`, whether `presentations` is at least 50. Every row is stated: a 0 is a counted
|
||
zero of a collected batch, never an unknown.
|
||
|
||
### 8.3 What a table states
|
||
|
||
- An accepted receipt means that two keys signed shares whose sum is the result, over the
|
||
reports of the batch the task's aggregators named (telemetry-aggregation §8.2), with
|
||
`reports_checked` saying whether the reports' ids, times and checksum were held to it.
|
||
- A row counts the presentations that receivers reported and that reached the leader before the
|
||
collection: a **lower bound**. It does not count receivers, people or sessions; it is not an
|
||
audience or a reach ([opportunity-report](opportunity-report.md) §10.3: "no record of this
|
||
text states an audience"); and it is never the *n* of bearer-receipts §7, where a larger *n*
|
||
lowers the bound per receiver and would read an unknown audience as the optimum.
|
||
- The time resolution is the batch, never finer than `time_precision`. A sparse hour is
|
||
collected inside a wider batch (DAP §6.3).
|
||
- A `rejected` count is the word of the aggregator that signed it (§7.3), and states what it
|
||
refused. It adds nothing to a row, and a row is not corrected by it.
|
||
- A party that publishes a table beyond the holders of its receipts withholds every row whose
|
||
`floor_met` is false (delivery-telemetry §8, its §8.4: "suppressed, not rounded") and
|
||
publishes no figure that is a sum over rows that include a withheld one: a total row, a
|
||
receipt's `report_count`, or a sum over creatives or regions.
|
||
|
||
## 9. Class, money and composition
|
||
|
||
- **A class of its own.** A table's counts are the class **aggregated**: never summed into
|
||
[revenue-share](revenue-share.md) §5.2's presented, beacons or unplaced, and never billable
|
||
under [receipt-ledger](receipt-ledger.md) §4.1's `presented_impressions` or
|
||
`billable_impressions`. A tariff of version 7 names their own unit,
|
||
`aggregated_impressions`, at a price of its own, and bills a receipt's count by it, once in a
|
||
chain (receipt-ledger §4.11). This is [settlement](settlement.md) §4.2's rule for its
|
||
unacknowledged class: "never billable as acknowledged". A price list of rung-price bills
|
||
delivery by the units of its §4.3 and takes no impression.
|
||
- **Never both.** A receiver whose claim of a presentation leaves the device (stitched-presentation,
|
||
stream-audit §6.3) makes no report of it, and one that reports it sends no claim of it.
|
||
- **No energy.** A table states no energy. A transmission's energy is bearer-receipts §7's, and
|
||
its bound never divides by a table count.
|
||
- **carousel.** The task file, the key-log revisions, the pod-input records, the manifests and
|
||
the creatives' objects ride as objects of groups on a carousel. A publisher **SHOULD** schedule
|
||
the task file's group so that carousel §6.4's "the group is held by *t*" holds with *t* at
|
||
least the time its receivers take to fill a token pool before the flight starts, and the
|
||
creatives' and records' groups by the flight's start. That is the checkable promise that
|
||
receivers within a loss bound can report attested presentations from the first hour. Nothing
|
||
in a count depends on the schedule.
|
||
- **timeliness.** A creative's deadline to be on a receiver before its break is a timeliness
|
||
entry of the creative's object, with `by_ms` no later than the splice (timeliness §8.5). A
|
||
publisher **MAY** also state one for the task file's object, its `by_ms` the flight's start
|
||
less the time its receivers take to fill a pool, so that carousel §6.4 at that `by_ms` is the
|
||
promise of the bullet above. A count depends on neither.
|
||
- **holdings.** A receiver that missed the task file or a creative fetches it from a neighbour
|
||
whose holdings record lists its group (holdings §5.4) and verifies it as any object (holdings
|
||
§6). The task file still needs its signer confirmed (§5.4). A neighbour with a path back is the
|
||
natural relay.
|
||
- **cache-authority.** A peer that served a creative is not a party to its count, and a tally of
|
||
the keys that held a creative by its deadline ([cache-authority](cache-authority.md) §8) counts
|
||
holding, never presentation: the two are never summed. Both write regions in rung-price §2's
|
||
codes.
|
||
- **bearer-receipts.** An upload file travels as a reconciliation does (§6.4). A reconciliation
|
||
attests receipt and a report counts a presentation: never the same fact, never merged. Key logs
|
||
on the bearer are bearer-receipts §8 (its §8.2): held, anchoring nothing, carrying revocations.
|
||
- **unidirectional-delivery.** The contribution is a sink-evaluated, total, deterministic
|
||
function (unidirectional-delivery §2), and the task authority is anchored out of band (§3
|
||
there).
|
||
- **age-assurance.** [age-assurance](age-assurance.md) §6 (its §6.1 to §6.3) holds device-held
|
||
type-0x0002 tokens on a one-way bearer in the same way; here the challenge is per report, not
|
||
per device.
|
||
- **live-rooms.** A binary record whose signer must hold a named authority in an origin's key
|
||
log is [live-rooms](live-rooms.md) §5's `live/room` and [capture-binding](capture-binding.md)
|
||
§6 (its §6.4) `capture/store`; `aggregate/task` is a third.
|
||
|
||
## 10. Reasons
|
||
|
||
The codes this extension adds, each with where it is raised:
|
||
|
||
| reason | raised at |
|
||
|---|---|
|
||
| `catalogue_malformed` | §4.3, §5.4 step 2, §8.1, §8.2 step 1 |
|
||
| `catalogue_mismatch` | §5.3 step 2 |
|
||
| `task_file_malformed` | §5.4 step 1 |
|
||
| `record_not_shared` | §6.2 step 2 |
|
||
| `record_terms_differ` | §6.2 step 3 |
|
||
| `creative_not_listed` | §6.2 step 5 |
|
||
| `instant_unknown` | §6.2 step 6 |
|
||
| `outside_task` | §6.2 step 7 |
|
||
| `token_exhausted` | §6.2 step 8 |
|
||
| `upload_malformed` | §6.4 |
|
||
| `receipts_overlap` | §8.2 step 3 |
|
||
|
||
This extension also raises: telemetry-aggregation §11's codes, `task_invalid`,
|
||
`measure_mismatch`, `floor_too_low` and `aggregator_alone` at §5.3, every code of its §8.1 at
|
||
§8.1, `batch_invalid` also at §8.1 step 12, and `task_mismatch` also at §8.2 step 2;
|
||
age-assurance §7.3's `count_mismatch` (a receipt's counts are not the counts they must be) at
|
||
§8.1 step 13; `truncated`, `malformed`, `unknown_version`, `bad_key` and `bad_signature`;
|
||
jwp-receipts §7.7's confirmation reasons at §5.4 step 5; stitched-presentation §9's codes and the
|
||
ad-presentation codes it reuses, at §6.2 steps 1 and 4; ad-presentation's `attestation_absent`
|
||
and `attestation_invalid` at §7.2; and DAP's report errors (DAP §4.1) and problem types (DAP
|
||
§3.6), by DAP's names, at §6.6 and §7.
|
||
|
||
## 11. Conformance
|
||
|
||
[`live-conformance/impressions/`](../live-conformance/impressions/) holds the corpus, written
|
||
from fixed seeds by `wai_impression_aggregate_vectors gen` as `vectors.json`. Its regions are
|
||
ISO 3166-1 user-assigned codes and their subdivisions, its origins reserved example domains, and
|
||
its issuer key RFC 9578's published test key. It has:
|
||
|
||
- **Catalogues**: the flight's and the open one's, the bounds read at their edge (cells, regions,
|
||
signers, keys, the hold), and a control for each rule of §4.3 and each way the layout breaks.
|
||
- **Tasks**: accepted ones, with the retention instant at the last instant exactly, and a control
|
||
for each step of §5.3, an overflowing retention instant among them.
|
||
- **Task files**: accepted ones, one whose lists name another suite first; a control for each
|
||
way a file is malformed; and the confirmation's refusals under the key logs held: a signer of
|
||
an unanchored origin, a key with `delivery` alone, a fetched anchor, a key revoked by a
|
||
revision received on the bearer, and the same key accepted before that revision is held.
|
||
- **Contributions**: each quartile at its mark, both carriages, every form of region, the hour,
|
||
flight and deadline edges, a pool whose least pair fails, a catalogue without attestation, and
|
||
a control for each step of §6.2.
|
||
- **Flows**, end to end, in telemetry-aggregation's flow form with the uploads and token
|
||
outcomes: an attested flight whose reports go through two overlapping relays, directly and
|
||
through a relay that merges two receivers' files; each kind of report §7.2 and DAP refuse; a
|
||
report the helper committed sent to it again, which it refuses and does not count (§7.3); a
|
||
collection held, then completed; a batch below the floor refused by both aggregators; and a
|
||
report uploaded after its batch's collection. A flight without attestation, the flight's
|
||
catalogue under another task, and a batch collected past the task's interval.
|
||
- **Uploads, plans and settlements**: files that read and a control for each rule of §6.4;
|
||
plans at each edge of the deadline, the retention instant and the budget; settlement of every
|
||
DAP report error, a value DAP does not define, and responses that settle nothing.
|
||
- **Receipts** (§8.1) accepted with and without their reports, with the two aggregators stating
|
||
different `rejected` counts in each order, with statements of version 1 and with a leader's of
|
||
version 2 beside a helper's of version 1, and refused at each
|
||
step this text adds, at the telemetry steps a receipt of another catalogue or task meets, for a
|
||
statement of a version not defined, and for a helper's count changed after it signed; **tables**
|
||
(§8.2) with a receipt given twice, a receipt given with and without its reports in both orders,
|
||
overlapping receipts, a receipt of statements of no count, a receipt of one statement of no count,
|
||
receipts of another task and catalogue, and the table's own refusals.
|
||
|
||
A conforming verifier gives every case the outcome, the reason and everything else the corpus
|
||
states. `wai_impression_aggregate_vectors verify` and the test `tests/impression_aggregate.rs`
|
||
check that the file regenerates byte for byte, that `wai-rs` gives every case its outcome, that
|
||
every reason of §10 appears, and that §10 lists exactly the reference's codes. The test also
|
||
checks, without storing it, that each standing receipt's result is the plaintext tally of the
|
||
cells of the contributions its reports came from, and that stitched-presentation's own
|
||
verification of the flight's claims, step 11 left out, gives per creative the presentations and
|
||
quartile counts the flight's table gives summed over its columns. The size bounds of §5.4 and
|
||
§6.4 are checked in code, since no vector carries them: a file of exactly the bound that reads in
|
||
every other respect is read, a task file then reaching step 2, and the same file one byte longer
|
||
is refused at step 1. §4.3's bound decides no outcome (§4.3), so no check can observe it.
|
||
`live-conformance/impression_aggregate_verify.py` is the second implementation, written from this
|
||
text: it shares no code with `wai-rs`, evaluates Prio3 and opens every report with the QoE
|
||
corpus's second implementation, judges records and claims with stitched-presentation's, verifies
|
||
tokens with ad-presentation's, confirms task-file signers with the signer corpus's, writes the
|
||
catalogue, the task file, the upload file, the contribution, the plan, the settlement and the
|
||
table itself, requires every mutated copy of the corpus to fail, and drops each of its own checks
|
||
in turn.
|
||
|
||
## 12. Security and privacy considerations
|
||
|
||
- **What the source learns.** Reporting gives up part of unidirectional-delivery §2's "the
|
||
source learns nothing": the collector learns batch sums over at least 50 presentations; each
|
||
aggregator learns each report's id, time bucket, length, task, validity and arrival; relays
|
||
learn the same, less validity; the issuer learns when a device refilled its pool.
|
||
- **Non-collusion** (telemetry-aggregation §13). The receipt names three keys and checks that the
|
||
aggregators' differ. Whether two operators pool their shares is in no byte.
|
||
- **Who may define a task.** Only a key that an origin's pinned key log lists with
|
||
`aggregate/task`. A task decides which two aggregators receive every receiver's shares, so a
|
||
fetched anchor, authority taken on first use, confirms none. A key-log revision on the bearer
|
||
revokes a task key on receivers with no path (bearer-receipts §8.2). A key compromised and not
|
||
yet revoked can name colluding aggregators for new tasks: the bound is the origin's
|
||
revocation.
|
||
- **Sybil.** Without **attest**, anyone holding a task file submits reports: the count is of
|
||
reports, not of presentations. A deployment that bills **SHOULD** set attest, as
|
||
stitched-presentation §4 (its §4.6) says of shared breaks. With attest, each counted report costs
|
||
a token issued to a device its attester vouched for, and the bound per device is the issuer's
|
||
issuance policy. A farm of genuine devices attests truthfully (ad-presentation §13).
|
||
- **The local verdict is the receiver's word** (unidirectional-delivery §2: determinism, not
|
||
enforcement). A modified receiver reports any cell in range; the range proof stops only values
|
||
out of range (DAP §2.2; telemetry-aggregation §13).
|
||
- **The aggregators' word.** `count_mismatch` catches a share that changes the integer sum or
|
||
wraps an element. It does not catch a share that moves counts between two cells that both stay
|
||
in range. A leader that holds a report's token (its private extension) can drop that report and
|
||
submit one of its own making under the same id and token, moving that presentation to another
|
||
cell. Neither adds a presentation. A correct result needs both aggregators honest (DAP §8;
|
||
telemetry-aggregation §8.2).
|
||
- **Small cells.** The batch floor of each aggregator, regions no finer than ISO 3166-2 and
|
||
buckets of at least an hour bound what a cell reveals. A collector that inserts reports it
|
||
knows shrinks a batch's effective set (DAP §8.1); attestation raises that cost. Differential
|
||
privacy (DAP §8.5) is not used, because these counts are billed, and noise in a billed figure is
|
||
not deterministic.
|
||
- **Linking.** Reports of one receiver uploaded together can be linked by their arrival, though
|
||
not by their values; relays that merge files, and Oblivious HTTP (DAP §8.4), break that. Tokens
|
||
are unlinkable to their issuance (RFC 9576 §3.3); a refill is a timing fact the issuer sees.
|
||
- **Lateness.** A receiver that never returns is never counted. The direction is always under.
|
||
- **No consent rule** (telemetry-aggregation §10). Where a regime asks, a `lawful-basis` claim
|
||
(jwp-receipts §2.4.6) states the basis.
|
||
- **Bounded work.** A catalogue is at most 1 MiB, a task file 1 MiB and an upload file 16 MiB,
|
||
each read once. A contribution is one stitched verdict and lookups; a task file's confirmation
|
||
is jwp-receipts §7.4's; a table is linear in receipts times *L*, plus a sort of the receipts.
|