Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/metric-qualifier-vendor-symmetry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"adcontextprotocol": minor
---

Fix the vendor-scope qualifier on `delivery-metric-aggregate` (previously a closed object with no properties, so only `{}` could validate) and add the optional 5-key qualifier to the vendor branches of `committed-metric`, `missing-metric`, `package-request` committed_metrics, the performance-feedback surfaces, and — critically — the `vendor-metric-value` delivery carrier, whose row uniqueness re-keys from `(vendor, metric_id)` to `(vendor, metric_id, qualifier)` so a vendor metric committed under two attribution windows is representable in the delivery report. Container tokens (`viewability`, `quartile_data`, `dooh_metrics`) are barred as value-bearing aggregate `metric_id`s — leaf identities exist for that. Matches what `canonical-reporting-commitment` already allows; a qualifier parity contract test now enforces an identical closed key set across every hand-maintained copy.
2 changes: 2 additions & 0 deletions docs/media-buy/task-reference/create_media_buy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,8 @@ When `confirmed_at` is `null`, sellers MUST omit `packages[].committed_metrics`.
- **`attribution_window`** — when `metric_id` is an outcome metric and the seller commits to a specific lookback window, the entry SHOULD carry `qualifier.attribution_window` as a structured duration (`{ interval: 14, unit: "days" }`). Two outcome rows over different windows are reported as separate rows so buyers don't accidentally aggregate across periods.

Without the qualifier, the contract is ambiguous and reconciliation falls back to whatever the delivery report happens to carry. The qualifier vocabulary is closed (`additionalProperties: false`); new keys ship explicitly in subsequent minors.

Vendor-scope entries MAY carry the same qualifier keys when the same vendor metric is committed under more than one methodology or window — e.g., `attribution_window` on a vendor outcome metric distinguishes a 14-day vendor attribution commitment from a 30-day one for the same `(vendor, metric_id)`. `missing_metrics` mirrors the qualifier for vendor entries exactly as it does for standard entries.
- **Reconciliation:** `missing_metrics` on [`get_media_buy_delivery`](/docs/media-buy/task-reference/get_media_buy_delivery) filters `committed_metrics` to entries where `committed_at < reporting_period.end`, then flags any that aren't populated in the report. A metric committed mid-flight is only audited from its commitment timestamp forward. Qualifiers are matched verbatim — a committed `{viewable_rate, mrc}` is not satisfied by a delivered `viewable_rate` carrying `viewability.standard: groupm`.
- **Optional in v1.** Sellers without per-package snapshot infrastructure can adopt incrementally. Absence is conformant but carries a known audit gap: without the snapshot, `missing_metrics` reconciles against the product's live `available_metrics` at report time, which may not reflect what was committed at create time. Sellers that omit `committed_metrics` accept this risk; buyers SHOULD treat absence as "no audit-grade contract" rather than "clean delivery." Expected to become required at the next major.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"deploy:cdn-artifacts-cutover:dry-run": "wrangler deploy --config workers/artifact-cdn/wrangler.cutover.toml --dry-run",
"verify:cdn-artifacts-cutover": "node scripts/verify-cdn-artifacts-cutover.mjs",
"typecheck": "tsc --project server/tsconfig.json --noEmit",
"test:schemas": "node tests/schema-validation.test.cjs && node --test tests/outcome-target.test.cjs tests/trusted-match-offer-creative-data.test.cjs tests/accessibility-violation-details.test.cjs tests/portfolio-routing-scope.test.cjs tests/catalog-item-availability-updates.test.cjs tests/compact-product-lifecycle-storyboards.test.cjs tests/timezone-resolution-storyboards.test.cjs tests/schema-deprecation-metadata.test.cjs tests/products-only-brief-compatibility.test.cjs tests/async-identity-convergence.test.cjs tests/creative-rotation.test.cjs tests/metric-identity-coherence.test.cjs tests/sort-contract-delivery-reporting.test.cjs tests/time-based-views-contract.test.cjs tests/lint-schema-enum-drift.test.cjs tests/synthetic-depiction.test.cjs tests/creative-rendering-authority.test.cjs && npm run test:premium-display-formats && npm run test:geo-region-targeting",
"test:schemas": "node tests/schema-validation.test.cjs && node --test tests/outcome-target.test.cjs tests/trusted-match-offer-creative-data.test.cjs tests/accessibility-violation-details.test.cjs tests/portfolio-routing-scope.test.cjs tests/catalog-item-availability-updates.test.cjs tests/compact-product-lifecycle-storyboards.test.cjs tests/timezone-resolution-storyboards.test.cjs tests/schema-deprecation-metadata.test.cjs tests/products-only-brief-compatibility.test.cjs tests/async-identity-convergence.test.cjs tests/creative-rotation.test.cjs tests/metric-identity-coherence.test.cjs tests/sort-contract-delivery-reporting.test.cjs tests/time-based-views-contract.test.cjs tests/metric-qualifier-parity.test.cjs tests/lint-schema-enum-drift.test.cjs tests/synthetic-depiction.test.cjs tests/creative-rendering-authority.test.cjs && npm run test:premium-display-formats && npm run test:geo-region-targeting",
"test:performance-feedback": "node --test --test-force-exit --test-timeout=30000 tests/performance-feedback-contract.test.cjs",
"test:dist-schema-version-ids": "node --test --test-force-exit --test-timeout=30000 tests/dist-schema-version-ids.test.cjs",
"test:examples": "node tests/example-validation-simple.test.cjs && npm run test:tmp-context-merge",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,10 +273,12 @@ phases:
get_media_buy_delivery and reconcile each package independently.

The semantic uniqueness key for each vendor metric row is
`(vendor.domain, vendor.brand_id, metric_id)`. Since brand_id is optional
in BrandRef, the operative key for single-brand vendors is
`(vendor.domain, metric_id)`. Sellers MUST NOT emit the same
`(vendor, metric_id)` pair twice in a single delivery report.
`(vendor.domain, vendor.brand_id, metric_id, qualifier)`. Since brand_id
is optional in BrandRef, the operative key for single-brand vendors is
`(vendor.domain, metric_id, qualifier)`. The same vendor metric MAY
appear in multiple rows only when each carries a distinct qualifier
(e.g., 7-day and 30-day attribution windows); sellers MUST NOT emit two
rows with the same tuple in a single delivery report.
steps:
- id: simulate_delivery_with_vendor_metrics
title: "Inject simulated delivery with vendor_metric_values"
Expand Down
27 changes: 27 additions & 0 deletions static/schemas/source/core/committed-metric.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,33 @@
"$ref": "/schemas/core/vendor-metric-id.json",
"description": "Identifier for the metric within the vendor's vocabulary."
},
"qualifier": {
"type": "object",
"description": "Optional qualifier disambiguating commitments to the same vendor metric measured under different methodologies or windows. Same closed key set as standard-scope entries; new keys ship explicitly.",
"properties": {
"viewability_standard": {
"$ref": "/schemas/enums/viewability-standard.json",
"description": "Viewability standard for this vendor metric, as a closed enum."
},
"completion_source": {
"$ref": "/schemas/enums/completion-source.json",
"description": "Completion attestation source for this vendor metric."
},
"attribution_methodology": {
"$ref": "/schemas/enums/attribution-methodology.json",
"description": "Attribution methodology for this vendor outcome metric."
},
"attribution_window": {
"$ref": "/schemas/core/duration.json",
"description": "Attribution window for this vendor outcome metric, as a structured duration."
},
"lift_dimension": {
"$ref": "/schemas/enums/lift-dimension.json",
"description": "Brand-lift dimension for this vendor metric."
}
},
"additionalProperties": false
},
"committed_at": {
"type": "string",
"format": "date-time",
Expand Down
26 changes: 24 additions & 2 deletions static/schemas/source/core/delivery-metric-aggregate.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
},
"metric_id": {
"$ref": "/schemas/enums/available-metric.json",
"description": "Identifier for the standard metric."
"description": "Identifier for the standard metric. Container tokens (e.g., `viewability`, `quartile_data`, `dooh_metrics`) MUST NOT appear here — an aggregate row carries a single numeric `value`, and leaf identities exist for exactly that purpose (e.g., `viewable_rate`, `quartile_75`)."
},
"qualifier": {
"type": "object",
Expand Down Expand Up @@ -177,7 +177,29 @@
},
"qualifier": {
"type": "object",
"description": "Optional qualifier keys for vendor metrics that need disambiguation (rare today — most vendor methodologies are intrinsic to the metric definition).",
"description": "Optional qualifier keys disambiguating this vendor-metric row from sibling rows under the same (vendor, metric_id) — e.g., attribution_window on a vendor outcome metric. Same closed key set as the standard branch; new keys ship explicitly.",
"properties": {
"viewability_standard": {
"$ref": "/schemas/enums/viewability-standard.json",
"description": "Viewability standard under which this row was measured. MRC and GroupM define materially different thresholds; never sum across standards."
},
"completion_source": {
"$ref": "/schemas/enums/completion-source.json",
"description": "Attestation source for a vendor completion-style metric — seller_attested from player/ad server, vendor_attested from an independent measurement path. Applicability is defined by the vendor metric; never sum across sources."
},
"attribution_methodology": {
"$ref": "/schemas/enums/attribution-methodology.json",
"description": "Attribution methodology under which this outcome row was computed (`deterministic_purchase`, `probabilistic`, `panel_based`, `modeled`). Outcome metrics measured under different methodologies represent materially different numbers; never sum across methodologies."
},
"attribution_window": {
"$ref": "/schemas/core/duration.json",
"description": "Attribution window for this outcome row. Object-valued duration (`{interval, unit}`), not a shorthand string. Outcome metrics measured over different windows represent the same metric over different time periods; the partition keeps them as separate rows so buyers don't accidentally aggregate."
},
"lift_dimension": {
"$ref": "/schemas/enums/lift-dimension.json",
"description": "Lift dimension this row represents (awareness, consideration, favorability, purchase intent, or ad recall) for vendor lift-style metrics. Applicability is defined by the vendor metric; each dimension is a separate surveyed outcome and rows under different dimensions must not be summed."
}
},
"additionalProperties": false
},
"value": {
Expand Down
2 changes: 1 addition & 1 deletion static/schemas/source/core/delivery-metrics.json
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@
},
"vendor_metric_values": {
"type": "array",
"description": "Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration.",
"description": "Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id, qualifier)` per reporting period — the same vendor metric MAY appear in multiple rows only when each carries a distinct qualifier (e.g., 7-day and 30-day attribution windows); sellers MUST de-duplicate before emission and MUST NOT emit two rows with the same tuple; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration.",
"items": {
"$ref": "/schemas/core/vendor-metric-value.json"
}
Expand Down
22 changes: 22 additions & 0 deletions static/schemas/source/core/missing-metric.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,28 @@
},
"metric_id": {
"$ref": "/schemas/core/vendor-metric-id.json"
},
"qualifier": {
"type": "object",
"description": "Mirrors the qualifier on the corresponding vendor-scope `committed_metrics` entry. MUST match that entry so reconciliation joins on (vendor, metric_id, qualifier).",
"properties": {
"viewability_standard": {
"$ref": "/schemas/enums/viewability-standard.json"
},
"completion_source": {
"$ref": "/schemas/enums/completion-source.json"
},
"attribution_methodology": {
"$ref": "/schemas/enums/attribution-methodology.json"
},
"attribution_window": {
"$ref": "/schemas/core/duration.json"
},
"lift_dimension": {
"$ref": "/schemas/enums/lift-dimension.json"
}
},
"additionalProperties": false
}
},
"required": [
Expand Down
22 changes: 22 additions & 0 deletions static/schemas/source/core/performance-feedback-metric.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,28 @@
},
"metric_id": {
"$ref": "/schemas/core/vendor-metric-id.json"
},
"qualifier": {
"type": "object",
"description": "Optional disambiguator mirroring the vendor-scope qualifier on `committed_metrics` — same closed key set as standard-scope entries.",
"properties": {
"viewability_standard": {
"$ref": "/schemas/enums/viewability-standard.json"
},
"completion_source": {
"$ref": "/schemas/enums/completion-source.json"
},
"attribution_methodology": {
"$ref": "/schemas/enums/attribution-methodology.json"
},
"attribution_window": {
"$ref": "/schemas/core/duration.json"
},
"lift_dimension": {
"$ref": "/schemas/enums/lift-dimension.json"
}
},
"additionalProperties": false
}
},
"required": ["scope", "vendor", "metric_id"],
Expand Down
22 changes: 22 additions & 0 deletions static/schemas/source/core/performance-feedback.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,28 @@
"metric_id": {
"$ref": "/schemas/core/vendor-metric-id.json",
"description": "Identifier for the metric within the vendor's vocabulary."
},
"qualifier": {
"type": "object",
"description": "Optional disambiguator mirroring the vendor-scope qualifier on `committed_metrics` — same closed key set as standard-scope entries.",
"properties": {
"viewability_standard": {
"$ref": "/schemas/enums/viewability-standard.json"
},
"completion_source": {
"$ref": "/schemas/enums/completion-source.json"
},
"attribution_methodology": {
"$ref": "/schemas/enums/attribution-methodology.json"
},
"attribution_window": {
"$ref": "/schemas/core/duration.json"
},
"lift_dimension": {
"$ref": "/schemas/enums/lift-dimension.json"
}
},
"additionalProperties": false
}
},
"required": ["scope", "vendor", "metric_id"],
Expand Down
22 changes: 22 additions & 0 deletions static/schemas/source/core/vendor-metric-value.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,28 @@
"description": "Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.",
"minimum": 0
},
"qualifier": {
"type": "object",
"description": "Optional qualifier disambiguating this row from sibling rows for the same (vendor, metric_id) — e.g., the same vendor outcome metric reported under 7-day and 30-day attribution windows. Same closed key set as `committed-metric`. When the matching `committed_metrics` entry carries a qualifier, this row MUST carry the identical qualifier so reconciliation joins on `(vendor, metric_id, qualifier)`.",
"properties": {
"viewability_standard": {
"$ref": "/schemas/enums/viewability-standard.json"
},
"completion_source": {
"$ref": "/schemas/enums/completion-source.json"
},
"attribution_methodology": {
"$ref": "/schemas/enums/attribution-methodology.json"
},
"attribution_window": {
"$ref": "/schemas/core/duration.json"
},
"lift_dimension": {
"$ref": "/schemas/enums/lift-dimension.json"
}
},
"additionalProperties": false
},
"breakdown": {
"type": "object",
"description": "Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level.",
Expand Down
Loading
Loading