feat(schemas): vendor-metric extensions surface for measurement metrics#3492
Merged
Conversation
Adds a structured parallel to the closed available-metric enum for vendor-defined metrics (Adelaide attention, Scope3 emissions, Nielsen DAR demographics, brand-lift surveys, in-flight attention panels). Resolves the closed/open enum question raised in #3460 with a structured surface instead of opening the standard vocabulary to free-form strings. - core/vendor-metric.json declares the descriptor (vendor BrandRef, metric_name, optional metric_category, optional standard_reference). - core/vendor-metric-value.json carries the reported value (vendor, metric_name, value, optional unit/measurable_impressions/breakdown). measurable_impressions is the coverage denominator following the decade-old viewability.measurable_impressions pattern. - enums/measurement-category.json — 9-value IAB-style classification. - reporting-capabilities.json adds vendor_metrics declaration array. - product-filters.json adds required_vendor_metrics filter (vendor, metric_name, or metric_category pin per entry). - delivery-metrics.json adds vendor_metric_values emission array. - Docs updated across get_products, get_media_buy_delivery, and optimization-reporting. No measurement_agent_url inlined — defers to brand.json agents[type='measurement'] discovery, matching the convention already established by measurement-terms.json and performance-standard.json. Vendor metrics are advisory in v1 (no missing_vendor_metrics accountability) — coverage is tracked via measurable_impressions. Refs #3460. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per diff feedback: metric_category and standard_reference are properties
of the vendor's metric definition, not the seller's per-product
declaration. They live on the vendor's brand.json
(agents[type='measurement']) and are discovered out-of-band, not
duplicated on every seller's extension.
Schema changes:
- vendor-metric.json strips to {vendor, metric_name} only. No category,
no standard_reference, no description, no documentation_url. Pointer-only.
- vendor-metric-value.json closed envelope (additionalProperties: false);
breakdown is the single escape hatch for structured payloads.
- product-filters.json required_vendor_metrics drops metric_category;
entries pin vendor and/or metric_name. Cross-vendor discovery is now
the buyer agent's job (resolve via vendor brand.json).
- enums/measurement-category.json deleted (no longer referenced).
- reporting-capabilities.json drops uniqueItems on vendor_metrics
(BrandRef optional fields defeat deep-equal); semantic uniqueness key
documented as (vendor.domain, vendor.brand_id, metric_name).
Other expert-review fixes applied:
- measurable_impressions absence semantics: "treat as unspecified, do
not compute coverage rate" (was "assume full coverage" — wrong vs
IAS/DV/MRC viewability convention).
- required_vendor_metrics[].metric_name carries the same regex pattern
as the schemas it filters.
- vendor_metric_values one-row-per-(vendor, metric_name)-per-period
documented in delivery-metrics.json.
- All real brand names removed from schema examples; replaced with
fictional placeholders (attentionvendor.example, emissionsvendor.example,
panelmeasurement.example).
- Doc references to specific vendors generalized.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per diff feedback: metric_name is functionally an identifier (stable lookup key within the vendor's namespace), not a display name. AdCP convention is `_id` for stable identifiers (brand_id, format_id, package_id, creative_id, media_buy_id); `_name` is reserved for human-readable display strings. - New core/vendor-metric-id.json shared schema (analogous to brand-id.json) with the regex pattern, bounds, and identifier semantics in one place. - vendor-metric.json: metric_name → metric_id (refs vendor-metric-id). - vendor-metric-value.json: metric_name → metric_id. - product-filters.json required_vendor_metrics[].metric_name → metric_id. - reporting-capabilities.json + delivery-metrics.json semantic-uniqueness keys updated: (vendor.domain, vendor.brand_id, metric_id). - Docs and changeset updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per diff feedback: vendor-metric.json wrapper carried only {vendor,
metric_id} — both required, both pulling from existing $refs. Two
fields with no fields-of-its-own to justify a dedicated schema file.
The wrapper added indirection without value.
The value side (vendor-metric-value.json) earns its own file: 6 fields
with bounds, regex, closed envelope, and the breakdown escape hatch.
Asymmetry is honest about what each surface is.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nt ships Per diff feedback: the canonical unit for a metric lives at the vendor's measurement-agent metric definition (brand.json agents[type='measurement']). The inline `unit` field on every value row is convenience today but becomes redundant once the measurement-agent surface formalizes per-metric unit declarations. Schema description now flags the disposition explicitly and notes the field MAY be deprecated when the agent surface lands. No behavior change — `unit` was already optional. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…esponsibility Three minor fixes from the final expert pass on PR #3492: - Add `x-entity: vendor_metric` to vendor-metric-id.json for consistency with brand-id.json:8 (`x-entity: advertiser_brand`). Standard ID-schema annotation that was missing. - Soften the `unit` field description in vendor-metric-value.json. The measurement-agent surface that will canonically declare per-metric units doesn't ship in 4.0 (early 2027 per release-cadence policy). Dropping the "MAY be deprecated" hint avoids signaling instability to SSP implementers building dashboards today; descriptive forward- reference to brand.json discovery is preserved. - Clarify dedup responsibility on the (vendor.domain, vendor.brand_id, metric_id) semantic uniqueness key. Sellers MUST de-duplicate before emission; buyers MAY treat duplicate rows as a seller-side conformance bug. Closes the ambiguity about which side enforces. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adding `x-entity: vendor_metric` to vendor-metric-id.json triggered the context-entity lint because the value wasn't in the registry. Add it to both the enum and the definitions map at static/schemas/source/core/x-entity-types.json, and to the editorial groupings in docs/contributing/x-entity-annotation.md alongside the existing `vendor_pricing_option` under Vendor services. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 29, 2026
bokelley
added a commit
that referenced
this pull request
Apr 29, 2026
…ts_sold, new_to_brand_units) (#3507) * feat(schemas): five missing metric scalars (CPCV, CPM, downloads, units_sold, new_to_brand_units) Adds five scalars production reporting carries today but had no enum entry: - cost_per_completed_view — CTV CPCV pricing scalar - cost_per_thousand — CPM (universal pricing scalar across CTV/display/ audio/DOOH; conspicuous absence next to cost_per_click before this PR) - downloads — IAB-standard scalar for audio/podcast (Podcast Measurement Technical Guidelines 2.x methodology); distinct from views - units_sold — Retail-media commerce scalar; distinct from conversions (one transaction may carry multiple units) - new_to_brand_units — Retail-media count of units sold to first-time buyers; parallel to existing new_to_brand_rate All five added as type:number minimum:0 properties on delivery-metrics.json plus enum entries on available-metric.json. Doc list in optimization-reporting.mdx updated. Closes the missing-scalars sub-item of #3460. Other sub-items already resolved by recent merges: - Closed-vs-open enum: resolved by #3492 (vendor-metric extensions) - completion_rate derived ratio: resolved by drop-carve-out call in #3472 DBCFM cross-check remains as a human follow-up (David Porzelt). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(schemas): cost_per_thousand → cpm, drop brand names, tighten descriptions Three review fixes per expert feedback on #3507: 1. Rename cost_per_thousand → cpm. Both protocol and product expert independently flagged that pricing-model.json:8 already uses the canonical "cpm" token; calling the scalar cost_per_thousand creates a translation mismatch. Operators say "CPM"; OpenRTB, GAM (eCPM), TTD all use cpm. Now buyers cross-walk pricing_model→reported scalar without a lookup table. 2. Remove real brand names from schema descriptions. delivery-metrics.json embedded a real list (Amazon Ads, Walmart Connect, Roundel, Sam's MAP, Instacart Ads, Target Roundel) that ships in the published spec — playbook policy bars real brand names in schemas. Replaced with neutral phrasing. (Bonus: Roundel IS Target's network, so the dup was confused.) Same for downloads (Megaphone/Art19/Spotify/Apple Podcasts list scrubbed). 3. Tighten descriptions: - cpm: fix operator-precedence prose to (spend / impressions) × 1000; expand channel list to include mobile/web video and native. - units_sold: note attribution windows are platform-specific; sellers SHOULD declare via measurement_windows or measurement_terms rather than encoding in the scalar name (no _14d/_30d sprawl). - new_to_brand_rate (existing field): clarify it's a fraction of conversions/transactions, distinguishing it from the new new_to_brand_units count. - new_to_brand_units: cross-reference the rate field for clarity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves the closed/open
available-metric.jsonenum question raised in #3460. Adds a structured parallel surface for vendor-defined metrics rather than opening the standard vocabulary to free-form strings.The motivating use case: a buyer that needs Adelaide attention, Scope3 emissions, Nielsen DAR demographics, IAS/DV custom quality, brand-lift surveys, or in-flight attention panels — none of which fit the closed standard enum, but all of which need structured discovery + reporting.
Why structured, not open-string
Opening the closed enum to
x_*strings would solve the asymmetry withdelivery-metrics.json'sadditionalProperties: trueposture but defeats discovery: a buyer asking "I need attention measurement" can't query a flat string namespace where Adelaide writesx_adelaide_attention_units, Lumen writesx_lumen_attention_seconds, and TVision writesx_tv_co_view_attention. A structured extension gives the buyer two queryable axes —vendor(BrandRef) andmetric_category(an industry classification) — with the standardmetric_nameas a third pin once vendors converge.Schemas added
core/vendor-metric.json— descriptor:{ vendor: BrandRef, metric_name, metric_category?, standard_reference? }. Noagent_url— measurement-agent discovery defers to the vendor'sbrand.jsonagents[type='measurement'], matching the existing convention inmeasurement-terms.jsonandperformance-standard.json(validated by both protocol expert and agentic architect on independent reviews).core/vendor-metric-value.json— reported value:{ vendor, metric_name, value, unit?, measurable_impressions?, breakdown? }. Themeasurable_impressionsfield is the coverage denominator — vendor measurement is rarely 100% (Adelaide only scores impressions where their SDK fires, Nielsen DAR only matches panel-resolved impressions, IAS/DV only measure where their tag is present). This pattern parallels the existingviewability.measurable_impressionsfield that has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade. Thebreakdownslot accommodates structured payloads beyond a single scalar (Nielsen demographic breakouts, TVision co-view, iSpot incremental decomposition).enums/measurement-category.json— 9 values:attention,brand_lift,incrementality,audience,reach,creative_quality,emissions,outcomes,other. Tracks the established measurement-vendor space.Wired in
core/reporting-capabilities.json→ newvendor_metricsarray (parallel toavailable_metrics)core/product-filters.json→ newrequired_vendor_metricsfilter (each entry pinsvendor,metric_name,metric_category, or any combination — at least one of three; AND across entries; cross-vendor query viametric_category; same filter-not-fail convention as siblings)core/delivery-metrics.json→ newvendor_metric_valuesarray (parallel to standard scalars;additionalProperties: truepreserved on parent for migration)docs/media-buy/task-reference/get_products.mdx→ new filter rowdocs/media-buy/task-reference/get_media_buy_delivery.mdx→ new bullet under per-packagedocs/media-buy/media-buys/optimization-reporting.mdx→ new Vendor-Defined Metrics section covering declaration, discovery, reporting, brand.json discovery anchor, the standards-driven promotion path, and v1 accountability scopev1 accountability scope
Standard
available_metricsare subject to themissing_metricscontract from #3472. Vendor metrics are advisory in v1 — buyers verify out-of-band viameasurable_impressionscoverage and direct calls to the vendor's measurement agent. The asymmetry reflects what the seller can credibly attest to: SSPs typically don't have Adelaide/Scope3 numbers in their delivery pipeline; those flow from the vendor's own infrastructure.Promotion path
When the industry converges on a metric via a published standard (IAB Attention Measurement Guidelines, MRC variants, GARM emissions framework), the spec adds it to the closed
available-metric.jsonenum and the vendor extensions become historical aliases. Thestandard_referencefield anchors promotion to standards-body publications, not to ad-hoc vendor convergence counts.Backwards compatibility
All additions are optional. Sellers without vendor metrics see no change. Closed
available-metric.jsonenum unchanged.additionalProperties: truepreserved ondelivery-metrics.jsonso existing free-form vendor emissions remain conformant; structuredvendor_metric_valuesis the recommended path going forward.Pre-build expert review applied
Three experts reviewed the design before code was written. Key adjustments incorporated:
descriptionanddocumentation_urlfrom on-product extension (architect: 50K-token context bloat in a typicalget_productsresponse — moved to brand.json, fetched lazily)currency→unit(free string accommodatesscore/seconds/persons/gCO2e/USD/etc. — product expert: single-currency field broke for non-monetary vendors)breakdown(free-form object) for Nielsen DAR / TVision co-view / iSpot incrementalmetric_categoryenum (architect + product expert: cross-vendor query viametric_namesilently fails because vendors don't converge on names pre-standardization)standard_referenceURI (product expert: "3+ vendors converge → promote" isn't how MRC/IAB standards actually emerge — wire promotion to published standards instead)missing_vendor_metricsaccountability array withmeasurable_impressionscoverage on each value (per your prior note: missing-ness is a coverage question, not a binary, and viewability solved this a decade ago)Test plan
npm run build:schemas— cleannpm run test:schemas— 7/7npm run test:examples— 34/34npm run test:json-schema— 255/255npm run test:composed— 32/32npm run typecheck— cleannpm run build:compliance— storyboard lints all greenDeferred to follow-ups
additionalProperties: true → falsemigration ondelivery-metrics.jsonat next majorsupported_extensionsdiscovery hint for capability discovery agentsRefs #3460. Closes the closed/open enum question.
🤖 Generated with Claude Code