feat(schemas): measurement capability block + brand.json metric_categories (#3612)#3652
Merged
Conversation
…ories (#3612) Add a `measurement` capability block to get_adcp_capabilities so measurement agents self-describe their per-metric catalog — the same pattern every other AdCP agent type already uses (sales/creative/governance/brand/buying/signals/rights). Adds optional `metric_categories[]` to brand.json's brand_agent_entry, paralleling rights agents' `available_uses[]` / `right_types[]` for coarse pre-call filtering by AAO. Schema additions: - enums/measurement-category.json: 9-value closed enum (attention, brand_lift, incrementality, audience, reach, creative_quality, emissions, outcomes, other) - protocol/get-adcp-capabilities-response.json: new measurement block with metrics[] (metric_id, category required; standard_reference, unit, description, documentation_url optional). Adds "measurement" to supported_protocols enum. - brand.json brand_agent_entry: optional metric_categories[] Closes #3612. Unblocks #3613 (AAO crawler + index implementation). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror governance.property_features[].methodology_url naming on the new measurement.metrics[] block. Same field, same purpose, same name. Reduces field-name drift across capability blocks for buyer agents generating code from the schemas. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three independent expert reviews (protocol/product/code) converged on
several fixes:
ARCHITECTURE — drop measurement from supported_protocols.
Per protocol expert: measurement isn't a tool-set with mandatory tasks
the way media_buy / signals / governance are; it's one discovery
surface. Following the compliance_testing / webhook_signing precedent,
the capability block's presence is the support signal — no companion
supported_protocols enum value, no compliance storyboard required.
Closes the "no measurement protocol storyboard exists" issue and
simplifies the architectural story.
ENUM — add three production-shipping categories that were missing.
Both protocol and product experts independently flagged: viewability
(MRC Viewable Impression Measurement Guidelines — IAS, DV, MOAT),
invalid_traffic (TAG/MRC IVT — HUMAN, DV, IAS), brand_safety (GARM
Brand Safety Floor + Suitability Framework). Without these, IAS/DV/
MOAT/HUMAN/GARM all fall back to "other" and the directory query
collapses for the most-shipping production categories.
ACCREDITATIONS — separate "implements a standard" from "third-party
certified."
Per product expert: standard_reference as free-form URL gives a false
signal of comparability — every vendor pastes the same MRC URL whether
accredited or not, and machine queries can't answer "show me MRC-
accredited vendors." Added structured accreditations[] array with
{ accrediting_body, certification_id?, valid_until?, evidence_url? }
shape. accrediting_body open string with examples (the global landscape
includes MRC/ARF/JIC bodies/ABC/BARB/AGOF — closed enum doesn't fit).
METHODOLOGY VERSION — added field to detect silent vendor methodology
changes.
Per protocol expert: vendors revising methodology silently is exactly
the trust problem standard_reference is meant to solve. methodology_
version optional; when present, buyer agents pin the contracted
version on committed_metrics; absence means buyer treats any change
as untracked.
PARITY — switched additionalProperties: true → false + explicit ext;
dropped description.maxLength: 1000 (governance has no cap); added USD
to unit examples (matches paired vendor-metric-value.json); moved
block placement to sit with peer protocol blocks before signing/
identity blocks; tightened "metric_id is the agent's identifier" prose.
STALE REFERENCES — fixed changeset's documentation_url references that
were left over from before the methodology_url rename. Fixed
[MeasurementCategory](#) dead anchor in docs.
DISCOVERY-VS-RATE-CARD CALLOUT — added explicit "this is a discovery
surface, not a rate card" paragraph in docs per product expert: pricing
per impression, minimum measurable inventory, attribution windows, geo
coverage, and SLAs are negotiated per buy via measurement_terms, not
through this catalog.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three pre-merge polish items per @bokelley feedback: 1. uniqueItems: true on measurement.metrics[] in get-adcp-capabilities-response.json. Duplicate metric_id within one vendor's catalog is unambiguously a vendor-side bug; cheap to enforce at schema level since metrics[] entries don't carry the BrandRef-tuple uniqueness concern that prevented uniqueItems on committed_metrics / missing_metrics elsewhere. 2. Locked-down example for the new capability shape in tests/ example-validation-simple.test.cjs. Two metrics (attention with full accreditations[]+methodology_version, emissions with the minimum required fields) plus a negative case proving the uniqueItems constraint actually rejects duplicates. Buyer-side implementers now have a canonical reference for the response shape before the WG locks it in. test:examples now 36/36 (was 34/34 — the two new tests are the positive + negative for measurement). 3. PR-body cross-reference to #3576 added separately on the GitHub PR to call out that this unblocks the buyer-proposed vendor-metric flow in #3576's request-side committed_metrics — without #3612, buyers can propose vendor metrics but have no way to discover which ones to propose. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pment) Reverses the earlier "drop from supported_protocols" call. Measurement will be a real protocol with its own task surface — reporting, attribution, panel queries — even though only get_adcp_capabilities ships in this PR. Putting measurement in supported_protocols now means the slot is correct; future tasks fit in without a re-architecture. Same model as every other protocol: creative is in supported_protocols AND has a capability block; governance same. Measurement follows. Cleanup paired: - supported_protocols enum: re-add "measurement" with description acknowledging the protocol is in development - enums/adcp-protocol.json: add "measurement" with kebab-case for cross-surface task categorization (was paired-enum gap protocol expert flagged in round 1) - docs/protocol/get_adcp_capabilities.mdx: rewrite measurement section intro to acknowledge the protocol-in-development state - tests: example uses ["measurement"] in supported_protocols (was faking it with ["sponsored_intelligence"] under the previous "block not protocol" call) - changeset: rewrite the architectural rationale section Solves the supported_protocols minItems:1 mismatch — measurement-only agents now have an honest value to populate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`enums/adcp-protocol.json` gained `measurement` in the capability-block PR; the matching TS const in `adcp-taxonomy.ts` had to follow or the `adcp-taxonomy` enum-sync test fails CI. BadgeRole stays narrow on purpose: - migration 453 CHECK constraint excludes `measurement` - no measurement specialism storyboards exist yet - no `ROLE_LABELS` entry to render a measurement badge `compliance-testing.ts` re-exports BadgeRole from `compliance-db.ts` instead of aliasing it to `AdcpProtocol`, and `VALID_BADGE_ROLES` in `badge-svg.ts` is an explicit literal mirroring that narrow list. When measurement specialisms ship, that PR widens BadgeRole + the migration + ROLE_LABELS in lockstep. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 30, 2026
…ck is the discovery surface WG pushback on the rights-agent precedent: right_types meaningfully partition who you'd ever call (a podcast buyer never wants CTV rights), but measurement categories are correlative — buyers typically want a basket (viewability + IVT + brand_safety travel together), so a brand.json coarse-filter doesn't reliably narrow the agent set. Capability blocks are queryable and cacheable; AAO crawls them on a TTL anyway. The brand.json field added schema surface, maintenance burden, and drift risk vs. the canonical catalog without buying useful filtering. Removes: - `brand_agent_entry.metric_categories[]` from `static/schemas/source/brand.json` - brand.json mentions from `enums/measurement-category.json` description (enum stays — still required on each metric in the capability block) - "Coarse-filter precedent on brand.json" callout from `docs/protocol/get_adcp_capabilities.mdx` - brand.json paragraph from `docs/registry/index.mdx` measurement-vendor section Changeset rewritten to explain the rationale. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… shape the taxonomy WG pushback on the closed category enum: - Categories overlap (brand_safety measurement vs. governance's content_standards), making the boundary fuzzy - No buyer-side discovery primitive consumes the field yet - Enum already drifting (Pia flagged display-centric gaps: VCR, quartiles, share of voice, ad pod) - metric_id + description + standard_reference + accreditations[] are already structured signal — AAO and buyer agents normalize from those without a coarse classification facet Removes: - `category` field (was required) on each metric in `protocol/get-adcp-capabilities-response.json` - `enums/measurement-category.json` (no remaining $refs) - Stale brand.json category prose in `core/reporting-capabilities.json` vendor_metrics description - Test fixtures and capability-block example Adds: - Scope subsection in `docs/protocol/get_adcp_capabilities.mdx` spelling out what claiming `measurement` actually means (parallel to compliance_testing / webhook_signing) If a category facet earns its keep once #3613's discovery primitive lands, it can be added back as an open vendor-asserted string with real query patterns shaping the taxonomy — rather than guessing upfront. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 tasks
bokelley
added a commit
that referenced
this pull request
May 1, 2026
…3613) (#3726) * feat(registry): measurement-vendor discovery on /api/registry/agents (#3613) Crawler ingests each measurement agent's get_adcp_capabilities.measurement block (AdCP 3.x, schema #3652) and the public /api/registry/agents endpoint gains three filters: - metric_id=attention_units (exact, repeatable, JSONB containment) - accreditation=MRC (exact, repeatable; verified_by_aao always false) - q=attention (substring on metric_id; v1 scope only) All three imply type=measurement; explicit non-measurement type returns 400. Filtering at SQL level so no live fan-out per request. sources counts recomputed against filtered set (sum(sources) === count invariant). Crawler calls get_adcp_capabilities on agents that expose the tool, parses the measurement block, and persists to new measurement_capabilities_json JSONB column. 10s timeout. A measurement fetch failure does not fail the whole discovery — other capability blocks still land normally. Per security review: - Per-field caps at write time (metrics ≤500, description ≤2000, metric_id ≤256, URI ≤2048, accreditations/metric ≤32). Reject — don't silently truncate — so failure is visible via discovery_error. - Belt-and-braces 256KB DB CHECK on the column. - Strip C0 controls + DEL (keep \t, \n). - Reject <script / javascript: / data:text/html / inline event handlers after NFKC normalization. - URI fields https-only in production. - q rejects %/_ outright (substring search, not pattern); remaining ILIKE escaping uses ESCAPE '\\' (mirrors catalog-db.ts:353 pattern, not brand-db.ts which omits the explicit ESCAPE clause). Closes #3613, closes #3614 (direct-call vs index doc folded into docs/registry/index.mdx). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(migrations): renumber duplicate 459 → 462 (resolves CI dup-migration block) Two migrations landed on main with prefix 459 (PR #3672 09:14 UTC, PR #3567 22:31 UTC) without renumbering. Subsequent PRs trip: - `No duplicate migration numbers` workflow check - `migrate.test.ts > has no duplicate migration version numbers on disk` - `loadMigrations` validation in `Server integration tests` and `Built migrations against Postgres` Renumber the later one (`459_create_type_reclassification_log` → 462) since 460 (identities) and 461 (measurement_capabilities, this branch) are already taken. Filename reference in `type-reclassification-log-db.ts` updated to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(measurement): clarify stripControlChars comment + add \r preservation test Reviewer noticed the comment claimed "\r is stripped" but the regex explicitly preserves 0x0D — exact intent was to keep all whitespace controls (\t, \n, \r), strip everything else in C0 plus DEL. Comment now matches code; test asserts \r survives. 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 May 17, 2026
4 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 a
measurementcapability block toget_adcp_capabilitiesso measurement agents self-describe their per-metric catalog — the same pattern every other AdCP agent type already uses (sales/creative/governance/brand/buying/signals/rights). Adds optionalmetric_categories[]tobrand.json'sbrand_agent_entry, paralleling rights agents'available_uses[]/right_types[]for coarse pre-call filtering by AAO.Closes #3612 (the protocol surface piece of #3586's per-metric catalog discovery).
Unblocks the buyer-proposed vendor-metric flow in #3576 — buyers can now know which vendor metrics to propose on
package-request.committed_metrics. Without this PR, the request-side vendor-scoped entries land partially blind.Unblocks #3613 (AAO crawler + index implementation) — schema for the data the crawler ingests.
The architectural anchor
Every AdCP agent type publishes capabilities at the agent itself via
get_adcp_capabilities. brand.json'sagents[]array is just the directory; capability info lives at the agent. The one partial exception is rights agents, which putavailable_uses[]andright_types[]on brand.json for coarse filtering — full pricing/terms still come from the live agent.This PR follows the rights-agent pattern for measurement — and per protocol-expert feedback, models the capability as a block (presence = support) rather than a
supported_protocolsvalue. Measurement agents have one surface (this catalog), not a tool-set with mandatory tasks the waymedia_buy/signals/governancedo. Same precedent ascompliance_testing/webhook_signingblocks.brand.jsonagents[type='measurement'].metric_categories[]get_adcp_capabilities.measurement.metrics[]/api/registry/measurement-vendorsSchemas added
enums/measurement-category.json— closed 12-value enum (attention,viewability,invalid_traffic,brand_safety,brand_lift,incrementality,audience,reach,creative_quality,emissions,outcomes,other). Includesviewability/invalid_traffic/brand_safety(MRC/TAG/GARM) per expert review.get-adcp-capabilities-response.json— newmeasurementblock withmetrics[]. Each metric carriesmetric_idandcategory(required), plus optionalstandard_reference,accreditations[](third-party certification — distinct fromstandard_reference),unit,description,methodology_url,methodology_version.additionalProperties: falsewith explicitextslot.uniqueItems: trueon the array.brand.jsonbrand_agent_entry— optionalmetric_categories[]array.Why
accreditations[]is separate fromstandard_referenceA metric can implement a published standard (URL pointing at the spec) without holding independent third-party accreditation. Buyers asking "is this MRC-accredited?" need a structured answer that survives URL parsing — every vendor pasting the same MRC URL whether accredited or not gives a false signal of comparability. The split surfaces the distinction at the schema layer.
Doc updates
docs/protocol/get_adcp_capabilities.mdx— newmeasurementsection with field table, response example showingaccreditations[]andmethodology_version, the discovery-vs-settlement framing, and an explicit "this is a discovery surface, not a rate card" callout (pricing/SLAs/coverage are negotiated per buy viameasurement_terms).docs/registry/index.mdx— refined measurement-vendor discovery section that points at the new capability block and forward-references the AAO index endpoint (AAO crawler + index for measurement-agent capabilities #3613) and the buyer-agent direct-call docs (Buyer-agent direct-call vs AAO-index pattern for measurement capabilities #3614).Worked example (locked into the test suite)
{ "supported_protocols": ["sponsored_intelligence"], "measurement": { "metrics": [ { "metric_id": "attention_units", "category": "attention", "standard_reference": "https://iabtechlab.com/standards/attention-measurement", "accreditations": [ { "accrediting_body": "MRC", "certification_id": "MRC-ATT-2026-001", "valid_until": "2027-12-31", "evidence_url": "https://mediaratingcouncil.org/accreditations/attentionvendor" } ], "unit": "score", "description": "Eye-tracking-based attention score (0-100). Computed from a panel of opted-in households.", "methodology_url": "https://attentionvendor.example/docs/attention-units", "methodology_version": "v2.1" } ] } }Backwards compatibility
All additions are optional and additive. Sellers without measurement capability are unchanged. Measurement vendors gain a structured catalog surface.
WG review
Hybrid design reached through the discussion thread on #3586 → #3612. Three independent expert reviews shaped the final shape: moved measurement out of
supported_protocols(capability-block pattern), added missing categories, addedmethodology_version, added structuredaccreditations[], switched toadditionalProperties: falsewith explicitext, locked an example into the test suite, addeduniqueItems: trueonmetrics[]. Labeledneeds-wg-review.Test plan
npm run build:schemas— cleannpm run test:schemas— 7/7npm run test:examples— 36/36 (added 2: positive measurement example + negative duplicate-rejection)npm run typecheck— cleanCloses #3612.
🤖 Generated with Claude Code