Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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-leaf-identities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"adcontextprotocol": minor
---

Add leaf metric identities so nested delivery values are individually declarable, committable, aggregatable, and sortable: `quartile_25`–`quartile_100` (resolving to `quartile_data.q1_views`–`q4_views`) and `viewable_rate`, `viewable_impressions`, `measurable_impressions`, `viewed_seconds` (resolving to the same-named `viewability` fields) join `available-metric` and `sort-metric`. This closes an existing contradiction: `committed-metric` qualifier rules and `delivery-metric-aggregate` conditionals already referenced these metric_ids, and the shipped `committed_metrics` / `metric_aggregates` examples were invalid against their own schemas. Also adds the missing flat transactional scalars (`commissionable_value`, `plays`, `cost_per_completed_view`, `cpm`, `downloads`, `units_sold`, `new_to_brand_units`) to `sort-metric`, with survey/model-based lift scalars documented as intentionally sort-excluded. Leaf identities resolve to the nested canonical values — no duplicate flat response fields are introduced. A metric-identity coherence contract test now enforces enum/schema/example agreement.
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` and `missing-metric`, matching what `canonical-reporting-commitment` already allows — vendor metrics measured under different attribution windows or methodologies are now distinguishable on the contract and reconciliation surfaces. A qualifier parity contract test now enforces an identical closed key set across every hand-maintained copy.
4 changes: 2 additions & 2 deletions docs/building/by-layer/L1/security.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ Implementations MUST keep three layers distinct:
2. **Credential** — the issuer-signed claim found through the reference or supplied as an embedded proof.
3. **Evaluation** — the receiving seller's or governance agent's result after applying its trust policy.

<!-- Using latest because these schemas are not yet released in any version.
Update to the correct version alias after the next release. -->
{/* Using latest because these schemas are not yet released in any version.
Update to the correct version alias after the next release. */}
The shared schemas are [`attestation-reference.json`](https://adcontextprotocol.org/schemas/v3/core/attestation-reference.json), [`attestation-issuer.json`](https://adcontextprotocol.org/schemas/v3/core/attestation-issuer.json), [`attestation-subject.json`](https://adcontextprotocol.org/schemas/v3/core/attestation-subject.json), and [`attestation-evaluation.json`](https://adcontextprotocol.org/schemas/v3/core/attestation-evaluation.json).

Every presentation identifies an issuer, an open URI-namespaced `claim_type`, a typed subject, and at least one delivery path:
Expand Down
4 changes: 2 additions & 2 deletions docs/building/operating/transport-errors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -563,8 +563,8 @@ JSON Schemas for all recommended `details` shapes are published alongside the er
- [`/schemas/v3/error-details/rate-limited.json`](https://adcontextprotocol.org/schemas/v3/error-details/rate-limited.json)
- [`/schemas/v3/error-details/budget-too-low.json`](https://adcontextprotocol.org/schemas/v3/error-details/budget-too-low.json)
- [`/schemas/v3/error-details/audience-too-small.json`](https://adcontextprotocol.org/schemas/v3/error-details/audience-too-small.json)
<!-- Using latest because account-moved.json is not yet released in any version.
Update to the correct version alias after the next release. -->
{/* Using latest because account-moved.json is not yet released in any version.
Update to the correct version alias after the next release. */}
- [`/schemas/latest/error-details/account-moved.json`](https://adcontextprotocol.org/schemas/latest/error-details/account-moved.json)
- [`/schemas/v3/error-details/account-setup-required.json`](https://adcontextprotocol.org/schemas/v3/error-details/account-setup-required.json)
- [`/schemas/v3/error-details/creative-rejected.json`](https://adcontextprotocol.org/schemas/v3/error-details/creative-rejected.json)
Expand Down
2 changes: 2 additions & 0 deletions docs/media-buy/media-buys/optimization-reporting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ Buyers receive the intersection of both. `impressions` and `spend` are always re
- **`new_to_brand_units`**: Unit-volume parallel to `new_to_brand_rate` — count of units sold to first-time brand buyers
- **`plays`**: Raw play count for DOOH/broadcast inventory (mirrors `forecastable-metric.plays`); distinct from `dooh_metrics.loop_plays` (per-screen rotation) and `impressions` (multiplied audience figure)

The numeric values inside `viewability` and `quartile_data` are individually addressable through leaf metric identities — `viewable_rate`, `viewable_impressions`, `measurable_impressions`, `viewed_seconds`, and `quartile_25`/`quartile_50`/`quartile_75`/`quartile_100` (which resolve to `quartile_data.q1_views`–`q4_views`). Leaf identities appear in `available_metrics` declarations, `committed_metrics` contracts, `metric_aggregates` rows, and breakdown `sort_by`; the nested object remains the only carrier of the values in delivery reports. In every metric set operation (declaration, filtering, intersection, selection) a container token subsumes its leaf identities — declaring `viewability` satisfies `viewable_rate`. Note that `quartile_100` counts 100%-of-duration completions (VAST `complete`), which is distinct from `completed_views` when the buy defines a shorter billable view threshold.

Buyers can optionally request a subset via `requested_metrics` to reduce payload size and focus on relevant KPIs.

For `completion_rate` and `quartile_data`, sellers MAY return `null` to signal the metric does not apply (e.g. on a non-video buy), and clients MUST accept `null` as a valid value for these two fields. Every other metric signals "not applicable" by omission — sellers omit it rather than sending `null`.
Expand Down
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 @@ -330,6 +330,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 docs/media-buy/task-reference/get_media_buy_delivery.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,7 @@ When you include `reporting_dimensions` in the request, the response includes di
}
```

Geo, device, audience, demographic, and placement dimensions accept optional `limit` and `sort_by` (any value from the `sort-metric` enum; defaults to `spend`). Geo requires `geo_level` (`country`, `region`, `metro`, `postal_area`). Demographic accepts optional canonical `age_ranges`. Each requested range must be supported by the product's `supports_demographic_breakdown.age`: `exact_predicates` accepts ranges within its declared bounds, while `enumerated_intervals` accepts only exact declared intervals. Unsupported ranges return [`UNSUPPORTED_FEATURE`](/docs/building/verification/compliance-catalog#error-code-unsupported-feature); the seller never substitutes a wider or narrower bucket. Omitting `age_ranges` requests the product's native declared breakdown. The spot log accepts only `limit`; its canonical order is `aired_at` ascending. Breakdowns are per-dimension only — cross-dimensional intersections are not supported.
Geo, device, audience, demographic, and placement dimensions accept optional `limit` and `sort_by` (any value from the `sort-metric` enum; defaults to `spend`). The sort vocabulary includes leaf metric identities that order rows by a value nested inside an object-shaped metric — `sort_by: "quartile_100"` orders by `quartile_data.q4_views`, `sort_by: "viewable_rate"` orders by `viewability.viewable_rate` — so queries like "top placements by in-view rate" need no flat duplicate fields. Survey/model-based lift scalars are excluded from sorting; see the `sort-metric` enum description. Geo requires `geo_level` (`country`, `region`, `metro`, `postal_area`). Demographic accepts optional canonical `age_ranges`. Each requested range must be supported by the product's `supports_demographic_breakdown.age`: `exact_predicates` accepts ranges within its declared bounds, while `enumerated_intervals` accepts only exact declared intervals. Unsupported ranges return [`UNSUPPORTED_FEATURE`](/docs/building/verification/compliance-catalog#error-code-unsupported-feature); the seller never substitutes a wider or narrower bucket. Omitting `age_ranges` requests the product's native declared breakdown. The spot log accepts only `limit`; its canonical order is `aired_at` ascending. Breakdowns are per-dimension only — cross-dimensional intersections are not supported.

### Available dimensions

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/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/schema-deprecation-metadata.test.cjs tests/creative-rotation.test.cjs tests/lint-schema-enum-drift.test.cjs tests/synthetic-depiction.test.cjs && npm run test:geo-region-targeting",
"test:schemas": "node tests/schema-validation.test.cjs && node --test 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/schema-deprecation-metadata.test.cjs tests/creative-rotation.test.cjs tests/metric-identity-coherence.test.cjs tests/metric-qualifier-parity.test.cjs tests/lint-schema-enum-drift.test.cjs tests/synthetic-depiction.test.cjs && 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
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
Loading
Loading