feat(schemas): required_metrics filter + missing_metrics accountability#3472
Merged
Conversation
Adds end-to-end metric accountability through the media buy lifecycle: - `filters.required_metrics` on get_products lets buyers declare metric capability requirements at discovery (e.g., `["completed_views"]` for a CTV CPCV buy). Sellers MUST silently exclude products whose `reporting_capabilities.available_metrics` is not a superset (filter-not-fail; do not return an error). Derived ratios are satisfied by their underlying scalars. - `by_package[].missing_metrics` on get_media_buy_delivery surfaces any metric the product's `available_metrics` advertised but the seller did not populate in this report. Empty (or absent) = clean; non-empty = accountability breach. Derived ratios MUST NOT appear here. Adds a measurement_accountability storyboard exercising the lifecycle from discovery through delivery. Schema-level contract is enforced now; semantic enforcement (verifying the seller honestly populates missing_metrics) is left to a follow-up test-controller extension. Refs #3460. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ration Expert review surfaced an inconsistency in the carve-out: ratios like `completion_rate` are listed in `aggregated_totals` (sellers ARE expected to report them per `delivery-metrics.json`) but were excluded from `missing_metrics` (so absence wasn't flagged). DSP UIs (TTD/DV360/Yahoo) also surface these as first-class report fields, not derivations. Drop the carve-out — `missing_metrics` is now the symmetric mirror of `available_metrics`, no name-class exceptions. Buyers/sellers self-resolve ratios via derivation when needed. Tightens window-maturation language from "should" to MUST per RFC 2119 guidance — prevents false-positive accountability breaches during the live window for broadcast/CTV channels with phased measurement (live → c3 → c7). Adds binding-contract one-liner to `required_metrics` description and `get_products.mdx`: the product's declared `available_metrics` is what flows into the buy and computes `missing_metrics` at delivery time. Tightens the storyboard with a `field_value_or_absent` check on `missing_metrics` to actually assert empty/absent on the clean path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 28, 2026
Contributor
Author
|
Issue #3482 proposes Generated by Claude Code |
Contributor
Author
|
Issue #3481 proposes Generated by Claude Code |
The test agent's simulate_delivery scenario only accepts impressions/spend/ clicks — not completed_views — so asserting field_present on it fails against today's controller. Schema validation (response_schema) and the missing_metrics empty-or-absent check still verify the contract surface. Semantic enforcement (seller actually populates the metric) stays deferred to the test-controller extension already tracked under #3460. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 tasks
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>
bokelley
added a commit
that referenced
this pull request
Apr 29, 2026
* feat(schemas): filter_diagnostics on get_products response Adds optional filter_diagnostics block to get-products-response.json — non-fatal observability for the filter-not-fail empty-result UX gap that both pre-build expert reviewers independently flagged on PR #3472. Shape: { total_candidates, excluded_by: { <filter_name>: { count, values?, notes? } } }. Optional, additive, counts only (never product names) to avoid competitive-intel leakage on adjacent campaigns or seller inventory. Disambiguates "no inventory matches the brief" from "your required_metrics filter excluded everything" without breaking the filter-not-fail convention. Sellers still silently exclude unmatched products; this block is observability, not error reporting. Counting semantics intentionally loose — sellers vary on ANY-filter vs ONLY-this-filter counting. Spec documents counts as approximate triage signal, not exact accounting. Tightening would force every seller to implement the same filter-evaluation ordering, which AdCP shouldn't mandate. Closes #3482. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(schemas): tighten filter_diagnostics — semantics field, closed envelope, typed values Three review fixes per expert feedback on #3508: 1. Add semantics: "only" | "any" | "approximate" enum on the parent filter_diagnostics block. Both reviewers flagged that "approximate" alone isn't RFC-2119 enough — buyers writing automated triage will file tickets when arithmetic doesn't add up. Sellers self-declare how their counts are computed; buyers inspect before doing math. Recommended value is "only" (counts products that would have been included if not for this filter alone — the value that's actually useful for "which filter killed my result set"). 2. Close the per-filter detail envelope (additionalProperties: false). Sellers wanting to extend should use the response-level ext field; the inner shape stays sealed. 3. Constrain values to oneOf: [{type:string},{type:object}] instead of the open items: {}. Each filter's value type is known; buyers can at least know they're dealing with strings or objects without a per-filter switch statement. Plus: document that total_candidates and excluded_by are independently optional — sellers whose baseline catalog size is sensitive MAY emit excluded_by without total_candidates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
Apr 29, 2026
…on create (#3510) * feat(schemas): package.committed_metrics — frozen reporting contract on create Adds package.committed_metrics and package.committed_vendor_metrics — frozen snapshots of the product's reporting_capabilities at create_media_buy response time. Closes #3481. Resolves the audit gap flagged on PR #3472: without per-package snapshots, missing_metrics is computed against the product's live state, so a 90-day-old buy is incorrectly judged "clean" when the seller quietly drops a metric they originally committed to. missing_metrics on get_media_buy_delivery now reconciles against committed_metrics when present, falling back to product.available_metrics when absent. Optional in v1 (sellers adopt incrementally; absence is conformant); MUST NOT change post-creation (update_media_buy cannot modify; renegotiation requires a new buy); snapshots full available_metrics, not the intersection with required_metrics. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(schemas): tighten committed_metrics — minItems, immutability, audit-gap Three review fixes per expert feedback on #3510: 1. Add minItems: 1 to both committed_metrics and committed_vendor_metrics. Protocol expert flagged that empty array vs absent created semantic ambiguity ("committed to nothing" vs "didn't snapshot"). Schema-level forbid eliminates the ambiguity — absence is the only "no snapshot" signal. 2. Tighten immutability prose: sellers MUST reject update_media_buy attempts to modify these fields with validation_error (suggested code IMMUTABLE_FIELD). Schema-level enforcement still requires a parallel change to package-update.json (filed as follow-up). 3. Acknowledge the fallback as a known audit gap. Both reviewers flagged that "optional with silent fallback" creates plausible deniability — the spec creates a contract that sellers can opt out of without buyers detecting it. Documented explicitly: sellers that omit committed_metrics accept the audit risk; buyers SHOULD treat absence as "no audit-grade contract" rather than "clean delivery." Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 tasks
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
Adds end-to-end metric accountability through the media buy lifecycle. Builds on the enum reconciliation in #3453.
filters.required_metricsonget_products— buyer declares the metric vocabulary they need at discovery (e.g.,["completed_views"]for a CTV CPCV buy). Sellers MUST silently exclude products whosereporting_capabilities.available_metricsis not a superset (filter-not-fail; do not return an error). Derived ratios (completion_rate,ctr,roas, etc.) are satisfied when their underlying scalars are supported — buyers should list the underlying scalars.by_package[].missing_metricsonget_media_buy_delivery— seller surfaces any metric the product'savailable_metricsadvertised but did not populate in this report. Empty array (or absent) = clean delivery; non-empty = accountability breach. Derived ratios MUST NOT appear here.measurement_accountabilitystoryboard — exercises the lifecycle from discovery → buy → simulated delivery → delivery report. Schema-level contract enforced now; semantic enforcement (verifying sellers honestly populatemissing_metrics) deferred to a follow-up that extends the test controller with metric-omission scenarios.No additional field is added to
create_media_buy— the product's declaredavailable_metricscarries forward as the contract;measurement_termsandperformance_standardsalready cover guarantee-level commitments at the package level.Why minor
Two additive optional fields and one new storyboard. No existing seller behavior changes; reports without
missing_metricsare interpreted as clean (field absent = no breach), and buyers omittingrequired_metricssee today's behavior.Backwards compatibility
required_metricsfilter only fires when populated;missing_metricsconsumers can ignore the field if not presentHint kind follow-up
A dedicated
metric_accountability_breachstoryboard hint kind (with Diagnose/Locate/Fix/Verify formatter) is deferred to a follow-up @adcp/client PR. For now, breach is detectable via standard schema validation on the delivery response and the runner'sfield_presentcheck on populated metrics.Refs #3460.
Test plan
npm run build:schemas— cleannpm run test:schemas— 7/7 passednpm run test:examples— 34/34 passednpm run test:json-schema— 255/255 passednpm run test:composed— 32/32 passednpm run typecheck— cleannpm run build:compliance— storyboard lints clean (scoping, branch-set, contradiction, context-entity, auth-shape, test-kits, pagination)test:unit,test:test-dynamic-imports,typecheck) — green🤖 Generated with Claude Code