Repository navigation
Measurement: declare seller-initiated trackers and impression-ID joinability #6878
Description
Summary
Seller agents can already inject their own measurement trackers at trafficking/assembly time — for agents without on-page presence or ad-server log access, it is the only way to build an independent impression count, and billing_measurement.vendor can already contract such a count as the invoicing authority. But the practice is protocol-invisible: nothing declares what a seller's reported numbers are counted from, that delivered payloads carry seller beacons absent from every buyer manifest, or whether {IMPRESSION_ID} makes the two sides' records joinable. This proposes small, additive declarations for all three, so counterparties select and contract on declared measurement posture instead of discovering it mid-dispute.
Current state (as of 7c155983 — re-verify at implementation time)
- The tracker-execution contract work (PR feat(creative): declare tracker execution contracts #6786, closing RFC: Measurement capability declaration (tracking_capabilities, attribution methodologies, TMP exposure data) #6207) is one-directional by design: "Seller-authored production commitment for first-class buyer-supplied tracker occurrences" (
static/schemas/source/core/tracker-execution-contract.json). core/measurement-terms.jsonalready provides the contractual hook:billing_measurement.vendoris "Vendor whose measurement of the billing metric is authoritative for invoicing" — a brand-ref that can name any measurement party, including a seller agent's own counting system. What's missing is the discovery-time declaration that would tell a buyer such a count exists and what it's based on.core/provenance.jsonestablishes the repo's precedent for honest-claim declarations: "Provenance is a claim by the declaring party — receiving parties should verify claims" — but it covers content/AI provenance only, not payload tracker injection.docs/reference/known-limitations.mdx: "The underlying delivery platform — or a buyer-specified measurement vendor — is the system of record for counting… AdCP is the wire and the contract, not the ledger." This proposal moves no system of record; it makes the counting source declarable so the existingbilling_measurementmachinery has something to name.core/reporting-capabilities.json(available_metrics,vendor_metrics,measurement_windows, …) is the established discovery-time declaration home.enums/universal-macro.jsondefinesIMPRESSION_ID("…minted by the publisher, ad-decision layer, or buyer at {TMPX} decode") — the minting vocabulary exists only as prose, with no per-product declaration and no statement of whether the value survives into any impression-level data.
Problem
- Unattributable attestations (primary). A seller agent without ad-server log access attests delivery on
get_media_buy_delivery, counted from its own trackers. The buyer assumes ad-server-sourced numbers. When variance exceedsbilling_measurement.max_variance_percent, the parties dispute a discrepancy whose dominant cause — begin-to-render tracker counting vs post-IVT ad-server counting — was never on the wire. Themeasurement_windowvocabulary (post_ivt,post_sivt) already acknowledges these stages matter for reconciliation; the counting source is the missing half. - Unreconcilable counts (primary). Even a disclosed independent count is only auditable record-by-record if impression identifiers join across parties. Two conformant sellers both substitute
{IMPRESSION_ID}; one preserves it in its impression-level data, one discards it after substitution. No surface distinguishes them before spend — this was RFC: Measurement capability declaration (tracking_capabilities, attribution methodologies, TMP exposure data) #6207'simpression_idbullet, orphaned when PR feat(creative): declare tracker execution contracts #6786 narrowed that RFC's 3.2 scope. - Undeclared payload beacons (secondary, severable). Delivered payloads already carry beacons no buyer manifest names (ad-server counting, publisher analytics), so today an extra seller pixel mostly passes unremarked. This changes when observation becomes machine-checked: spec(creative): implement preview event and macro observation sessions #6782 preview-observation sessions will intercept outbound actions and compare them against declarations, and buyers with strict data-governance terms increasingly enumerate permitted calls. A declaration makes the seller's beacon expected rather than anomalous. This field is the weakest-evidenced of the three and is explicitly severable if the WG prefers to wait for spec(creative): implement preview event and macro observation sessions #6782.
Proposed change
All additions optional; declarations normative when present; absence means undeclared, never unsupported (per #6207 and #6786 convention).
1. reporting_capabilities.impression_id — top-level, because joinability matters regardless of who counts (a buyer vendor reconciling against ad-server logs needs it as much as a seller-agent count does):
"impression_id": {
"minted_by": "ad_decision",
"available_in_delivery": true
}minted_by: new enums/impression-id-source.json, values publisher | ad_decision | buyer_at_tmpx — verbatim from the merged macro's prose, no new vocabulary. available_in_delivery: whether the minted value is preserved as a join key in whatever impression-level surface the seller offers (log-level delivery per #2196, TMP exposure data, or out-of-band export). true commits to key-preservation; the pipe and record schema stay #2196's scope.
2. reporting_capabilities.counting_sources[] — what the seller's reported delivery numbers are counted from; an array, because real deployments are hybrid (own trackers for some inventory, ad-server aggregates elsewhere):
"counting_sources": ["seller_tracker", "ad_server"]If #2041's measurement_sources vocabulary lands first, this MUST share it rather than fork — #2041 attributes numbers at report time; this declares posture at discovery time.
3. reporting_capabilities.seller_tracker_events[] (severable) — events for which the seller injects its own trackers into delivered payloads, values from enums/pixel-tracking-event.json for pixel-shaped injection, with VAST/DAAST-assembled <Impression> insertion declared via the impression value.
On conformance paths, directly: #6207's triage blocked substituted_macros_in_trackers[] for conflicting with DR-0005 — no wire-observable conformance path. These fields differ in kind: they are honest-claim declarations on the provenance.json model ("a claim by the declaring party — receiving parties should verify"), whose enforcement hook is contractual (billing_measurement.vendor, data-governance terms) rather than a compliance probe. available_in_delivery additionally becomes probe-able the moment #2196 lands (request the LLD surface, assert the key is present), and seller_tracker_events[] becomes probe-able under #6782 observation. counting_sources[] alone never gets a wire probe — the proposal accepts that openly and argues the declaration still pays for itself as the thing counterparties contract against; if the WG holds the DR-0005 line strictly, that field can be dropped without weakening the other two.
Privacy note: a seller-injected tracker is a data-collecting network call; declared trackers operate within the publisher's consent scope, and this declaration composes with (does not solve) consent-scope propagation, tracked in #2540.
Deliberately untouched: firing mechanics (no seller-side counterpart to tracker_execution_contract — the seller needs no buyer permission to measure its own delivery); get_media_buy_delivery aggregate shapes; billing_measurement (the hook already exists); attribution semantics; the LLD record schema (#2196); trusted_match.exposure_data (#6207's other orphaned bullet — flagged, not revived here).
Alternatives considered
- Extend Measurement source attribution on delivery reports #2041 instead of adding
counting_sources[]. Genuinely viable — its refactor discussion is already generalising the source enum. Acceptable resolution: this issue carries fields 1 and 3, andcounting_sources[]lands as a comment on Measurement source attribution on delivery reports #2041. Either works; stated preference is one coherent declaration block, but the vocabulary must be shared regardless. - Per-representation disclosure instead of product-level
seller_tracker_events[](near Creative delivery evidence: manifest hashing, retention contracts, and evidence references #6349's evidence work): more precise, much heavier; product-level can ship first. - Do nothing. Seller-side counting keeps working as a hidden hack: undisclosed beacons, variance disputes whose cause is invisible, and joinability discovered post-spend. Since
billing_measurement.vendorcan already name a seller count as authoritative, doing nothing leaves the protocol able to contract on a measurement source it cannot describe.
Compatibility
Additive, optional, minor bump. Older clients ignore the new fields. No 3.x backport.
Files affected (as of 7c155983)
static/schemas/source/core/reporting-capabilities.json— three new optional fieldsstatic/schemas/source/enums/impression-id-source.json— new enumstatic/schemas/source/index.json— registrationdocs/media-buy/task-reference/get_media_buy_delivery+ billing-authority page — counting-source paragraph; cross-link from theIMPRESSION_IDmacro entry- Test vectors for declaration shapes; behavioural scenarios deferred to the surfaces that make them observable (Clean room and log-level delivery for reporting (3.1) #2196 for joins, spec(creative): implement preview event and macro observation sessions #6782 for injection)
Related
- RFC: Measurement capability declaration (tracking_capabilities, attribution methodologies, TMP exposure data) #6207 — parent RFC; re-homes its
impression_idbullet after the 3.2 scope cut - PR feat(creative): declare tracker execution contracts #6786 — the buyer-supplied mirror image (discussion)
- Measurement source attribution on delivery reports #2041 — report-time measurement-source attribution; must share vocabulary
- Clean room and log-level delivery for reporting (3.1) #2196 — log-level delivery epic; owns the pipe
available_in_deliverypoints at, and the probe for it - Add IMPRESSION_ID universal macro for impression dedup #5020 (merged) — the
IMPRESSION_IDmacro - spec(creative): implement preview event and macro observation sessions #6782 — preview observation; the eventual probe for
seller_tracker_events[] - Creative delivery evidence: manifest hashing, retention contracts, and evidence references #6349 — delivery evidence; the heavier per-representation disclosure path
- Consent-scope propagation across protocol boundaries #2540 — consent-scope propagation; composes with the privacy note
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
bokelley commentedon Aug 25, 2026
Triage
Classification: Feature request (spec proposal)
Bucket(s): spec/protocol
Status: ready-for-human
What the experts said:
impression_idplacement. DR-0005 does not apply (honest-claim declarations, not conformance probes). CONCERN oncounting_sources[]null-state semantics (is omission "unknown," and must sellers who have setbilling_measurement.vendorto their own count declare this?). BLOCK on vocabulary sequencing — field 2 must share an enum with Measurement source attribution on delivery reports #2041 before landing or discovery-time and report-time nomenclature will fork permanently. Also flags:canonical-reporting-capabilities.jsonhasadditionalProperties: false; new fields must be declared there too (not mentioned in the issue).reporting_capabilitiesis output-focused;counting_sources[]andimpression_idare counting-methodology inputs that may sit more naturally underbilling_measurementwhere the invoicing authority already lives. Same vocabulary-coordination concern as above.My take: Both experts confirm the gap is real and the fields are non-breaking. They split on placement; vocab sequencing with #2041 is an agreed gate regardless. Two WG calls needed before implementation can start.
Decision 1 — Placement (experts split) — @bokelley, your call:
Option A — Fields in
reporting_capabilitiesas proposed:Pro: follows existing pattern (
measurement_windows,vendor_metrics);available_in_deliveryis inherently a reporting-surface capability. Con: counting source is a methodology input, not a reporting output — may confuse implementers.Option B — Fields nest under
billing_measurement(or a newmeasurement_postureobject):Pro: co-located with the contracting authority that already names the invoicing count. Con:
impression_idjoinability is useful regardless ofbilling_measurementconfig; forcing it here conflates declaration scope.Option C (hybrid):
impression_idinreporting_capabilities;counting_sourcesinbilling_measurement. Cleaner semantics at the cost of split placement.Decision 2 — Vocabulary sequencing with #2041 — WG call:
counting_sources[](discovery-time) andmeasurement_sources[]from #2041 (report-time) must share an enum. Either this issue takes ownership ofenums/counting-source.jsonand #2041 imports it, or field 2 is formally blocked on #2041. The vocabulary must be shared regardless; the WG decides who leads.Field 3 (
seller_tracker_events[]): Both experts APPROVE deferral pending #6782. Also note:enums/pixel-tracking-event.json(referenced in the issue) does not exist;vast-tracking-event.json/daast-tracking-event.jsondo — the severed issue will need to resolve this.Triaged by Claude Code. Session: https://claude.ai/code/session_011gsUyQRZDe4Eaz1govfaTu
Generated by Claude Code
lukasz-pubx commentedon Aug 25, 2026
Thanks for the fast triage — responding on both decisions and the concerns, and folding in the 3.3 framing agreed with @bokelley on #6786 (his reply routes both surviving surfaces to 3.3, which this issue should be read as targeting; happy to see it labeled accordingly).
Decision 1 (placement): as the issue author, Option C — accepted as an explicit re-scoping, not just a relocation. The product-expert's concern is right:
product.measurement_termsalready exists as seller defaults — "Declares who counts the billing metric" — socounting_sources[]next tobilling_measurement.vendorstays pre-buy discoverable, andvendor/counting_sourcesbecome who counts / how it's counted for the same metric. But being precise about what moving it changes:measurement_sources[], and the two compose cleanly instead of overlapping. I'll re-frame Problem 1 in the body accordingly.measurement_terms, a buyer can proposecounting_sourcesand force accept /TERMS_REJECTED/ adjust on the seller's methodology. Naming it as intentional: buyer-visibility is the requirement, buyer-negotiability is an acceptable consequence — the seller retains rejection. If the WG wants visibility without negotiability, that argues for Option A; flagging the fork so it's decided, not inherited.impression_idstays inreporting_capabilitiesper C: joinability is a property of the seller's reporting/log surfaces regardless of billing configuration — exactly the con Option B was flagged for. Also adopting the product-expert's framing gladly: pre-buy filtering is the dominant use, and both fields should read as selection inputs first, dispute forensics second.Once Decision 1 is called, I'll edit the issue body to match (placement, re-scoped Problem 1, corrected Files affected) so the body stays the source of truth rather than this thread.
Decision 2 (vocabulary sequencing): mild preference that this issue owns
enums/counting-source.json, with #2041 importing and extending. This issue is narrow and 3.3-targeted while #2041's refactor is still settling scope; a small enum landing here unblocks both. It should be drafted as the union from day one —seller_tracker | ad_server | publisher_logs | measurement_vendorreconciled with #2041's broadcast set (set_top_box,acr,panel,server_logs,sdk,census), with overlaps collapsed deliberately (server_logsvspublisher_logs/ad_serverneeds one answer, not near-synonyms). Happy to draft the merged enum for review. If the WG prefers #2041 to lead, formally blocking field 2 on it is equally acceptable — shared vocabulary is the requirement; ownership isn't.Null-state semantics (ad-tech-protocol-expert): omission means undeclared — never "unknown-but-probably-ad-server" — consistent with the issue's convention. On the self-vendor question, two options for the WG:
billing_measurement.vendorSHOULD declarecounting_sources[](docs-level prose; zero enforcement).vendorresolves to the seller's own domain,counting_sources[]is required — schema-expressible, testable, and notably it hands the field the wire-observable conformance path it otherwise lacks, closing the residual DR-0005 exposure.Author preference: the conditional MUST — it converts the field's weakest property into a probe.
Corrections accepted, with one sequencing clarification:
canonical-reporting-capabilities.json(additionalProperties: false) — correct, missed in Files affected.impression_idmust be declared there too (it's a selection input; the compact projection should carry it). Under C,counting_sources[]instead touchescanonical-measurement-terms.json— sameadditionalPropertiescheck applies there. Both go into the body edit.enums/pixel-tracking-event.json— correct that it doesn't exist onmaintoday: it's introduced by PR feat(creative): declare tracker execution contracts #6786 (extracted from the inlinepixel-tracker-asset.jsonenum). Since feat(creative): declare tracker execution contracts #6786 is 3.2-beta and this issue is 3.3, the reference resolves before the severed field-3 issue lands — a sequencing dependency for that issue, not a defect here.Field 3: agreed all around — stays severable, and I'll file it as its own issue framed around #6782's observation surface once that work is far enough along to name the probe (per the #6786 discussion), carrying the enum-sequencing note above.
bokelley commentedon Aug 29, 2026
Agree this belongs in 3.3; the issue is now assigned to the
3.3.0milestone. Before the schema shape is treated as implementation-ready, three constraints need to be reflected:minted_byoverstates what discovery can promise when the source may vary per impression or execution path. The discovery declaration should expresspossible_sources[]; if the actual source is needed for reconciliation, it belongs on the eventual impression-level record. This also matches the deferral recorded when RFC: Measurement capability declaration (tracking_capabilities, attribution methodologies, TMP exposure data) #6207 was narrowed.available_in_delivery.get_media_buy_deliveryis aggregate reporting and cannot carry an impression-level key. The capability should name the concrete join/export surfaces on which the identifier is preserved (with Clean room and log-level delivery for reporting (3.1) #2196 owning transport and record shape), rather than a boolean that can be misread as presence in the aggregate delivery response.tracker_sourceonly as a manifest asset and explicitly excludes trackers discovered inside fetched/assembled VAST, DAAST, or JavaScript. Seller-added disclosure therefore needs either an explicit seller-added/assembled source branch in spec(creative): implement preview event and macro observation sessions #6782 or a separate representation/readback surface; it does not become observable merely because spec(creative): implement preview event and macro observation sessions #6782 lands as currently scoped.The counting-source vocabulary should still be shared with #2041. Placement of billing-count methodology under
measurement_termsversus general reporting posture remains the WG decision described above; these constraints apply either way.bokelley commentedon Oct 6, 2026
WG decision brief (3.3)
Problem. A seller can bill from its own count (
billing_measurement.vendorcan already name it), but nothing on the wire says what that count is built from. Variance disputes then turn on a cause nobody could see before spend (server-side logs vs begin-to-render beacons). #2041 is the report-time twin: a vendor number can be set-top-box, ACR, panel, or a fusion, and buyers must followmethodology_urlto learn which. Pulling for it: @lukasz-pubx (#6878, CTV/SSAI seller-side counting); broadcast measurement feedback from Greg (O-N-X) behind #2041, with VideoAmp/Nielsen-style fusion as the motivating case. #2041 is also tracked as WG-ready (draft DR in-thread).Proposed shape. Draft PR: #7940 (additive, minor, tests green). One shared closed vocabulary of collection mechanisms (
set_top_box,acr,panel,server_logs,client_tracker,sdk) used twice. Parties stay in the BrandRef, soad_server/publisher_logscollapse intoserver_logs.{ "vendor": { "domain": "tunein-measurement.example" }, "metric_id": "tune_in_reach", "value": 1500000, "measurement_provenance": { "source_types": ["set_top_box", "acr"], "deduplicated": true } }Absence of either means undeclared. Both are honest-claim declarations enforced through the contract, not conformance probes (DR-0005 not engaged). DR-XXXX (
proposed) records the ruling; the memo's DR-0020 number is taken.Already resolved with the protocol and product experts (no vote needed):
source_typesis a unique, order-insignificant set listing only sources that enter the value;counting_sourceslists only sources that enter the billed count (cross-checks excluded);deduplicatedabsent = unspecified, andfalsemust be serialized; party/ownership labels are not enum values;counting_sourcesalso lands incanonical-measurement-terms.json; provenance is not aqualifierkey.Open questions for the WG (each answerable yes/no or pick-one):
counting_sources[]underbilling_measurement(buyer-negotiable, seller canTERMS_REJECTED) or inreporting_capabilities(visible only)? Rec:billing_measurement. Keeps who-counts and how-counted on one term, and sellers keep rejection.measurement-source-type.json, carried by feat(schema): experimental collection-mechanism vocabulary for counting_mechanisms and measurement_provenance #7940 and used by both issues, or this issue ownscounting-source.jsonand Measurement source attribution on delivery reports #2041 imports it? Rec: the former. One PR lands both uses, so ownership is moot and the Measurement source attribution on delivery reports #2041 name stays.custom/URI escape as in RFC: geo_cells / geo_cells_exclude targeting — hierarchical cell-grid systems as a declared geographic unit #5969 and feat(media-buy): add identifier-based place targeting #6093? Rec: closed. Adding a value is a minor, vendor nuance hasmethodology_url/breakdown, and an escape would add URI governance for six values.census. Drop it from the enum (draft as proposed in Measurement source attribution on delivery reports #2041 had it)? Rec: drop. It is a coverage claim, not a mechanism, so[server_logs, census]and[census]encode one count three ways, andset_top_boxwithoutpanelalready says "not projected". Values can never be removed once shipped; a dropped value can return later.billing_measurement.vendoris the seller (or its ad server), iscounting_sources[]a conditional MUST or a SHOULD? Rec: SHOULD in 3.3, MUST viaschema_required_whenas a 4.0 candidate. A MUST is expressible (feat(schema): typed vendor_relationship and play-based coverage denominators for vendor metrics #7177 did it forvendor_relationship), butbilling_measurementis optional, so a MUST invites omitting it instead of disclosing.measurement_provenanceon eachvendor_metric_valuesrow (yes), or vendor-capability level only? Rec: row-level. A vendor's metrics draw on different sources (reach from STB+panel, tune-in from STB alone); costs one optional object. A vendor-levelpossible_source_types[]is a clean follow-up.deduplicatedboolean. Keep it (true= across all listed feeds,false= simple union), or omit until a vendor needs household/person/device levels? Rec: keep;falseis the one signal that overlap may double-count, and adeduplication_levelcan be added later because the object is open.measurement_provenanceopen per DR-0009 (yes), or closed as drafted in-thread? Rec: open. It is a descriptor, not a join key, so DR-0019 Class A does not apply; unknown keys are ignorable.impression_idsequencing. Confirm yes/no: (a)seller_tracker_events[]is formally deferred to a separate issue after spec(creative): implement preview event and macro observation sessions #6782 grows a seller-added/assembled-payload branch; (b)impression_idships with Clean room and log-level delivery for reporting (3.1) #2196 (milestone 3.4.0), usingpossible_sources[]plus an explicitjoin_surfaces[]list, notminted_byoravailable_in_delivery. Rec: yes to both. No impression-level surface exists to name yet, andpossible_sources[]alone would lock a second closed enum with no consumer.Out of scope. 3.4:
impression_id(reporting_capabilitiespluscanonical-reporting-capabilities.json), aget_productsfilter oncounting_sources, adeduplication_level, vendor-levelpossible_source_types[], report-time provenance for standard scalars likeimpressions,ssai_logs, sensor values for DOOH. Blocked on #6782:seller_tracker_events[]. 4.0: MUST for self-counting sellers.feat(schema): experimental collection-mechanism vocabulary for counti…
bokelley commentedon Oct 31, 2026
Part of this shipped: #7940 (3.3.0-beta.2, experimental
media_buy.measurement_provenance) added thecollection-mechanismvocabulary,billing_measurement.counting_mechanisms[]and row-levelvendor_metric_provenance[].Not shipped, so per the WG rule ('anything not decided moves to 3.4') only the remainder moves to 3.4:
impression_idjoinability (with #2196) andseller_tracker_events[](blocked on #6782), plus theget_productsfilter on counting mechanisms. Objections by Fri 2026-11-06. The milestone stays 3.3.0 for now; it will be moved after Nov 6.