Skip to content

feat(compliance): move storyboards into protocol + domains/specialisms model (#2176)#2265

Merged
bokelley merged 8 commits intomainfrom
bokelley/issue-2176-release
Apr 17, 2026
Merged

feat(compliance): move storyboards into protocol + domains/specialisms model (#2176)#2265
bokelley merged 8 commits intomainfrom
bokelley/issue-2176-release

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Closes #2176.

Moves AdCP compliance storyboards out of @adcp/client and into the protocol repo, publishes them alongside schemas at /compliance/{version}/, and introduces the two-axis capability model (domains × specialisms) that get_adcp_capabilities will use going forward.

Summary

  • /compliance/ treeuniversal/, domains/, specialisms/, test-kits/, index.json. YAML is the authoritative source for specialism→domain mapping (domain: frontmatter required, build fails on enum drift).
  • /protocol/{version}.tgz bundle — single gzipped artifact with schemas/ + compliance/ + openapi/ + README.md + CHANGELOG.md, wrapped under adcp-{version}/ for safe extraction. SHA-256 sidecars + immutable caching. New /protocol/ discovery endpoint lists versions.
  • get_adcp_capabilities taxonomy:
    • domains[] — 6 broad categories: media-buy, creative, signals, governance, brand, sponsored-intelligence (promoted from specialism)
    • specialisms[] — 21 claims, each rolling up to one domain
      • Renames: broadcast-platformsales-broadcast-tv, social-platformsales-social
      • Merges: property-governance + collection-governanceinventory-lists
      • New for 3.1: sales-streaming-tv, sales-exchange, sales-retail-media, measurement-verification
  • Docs: new docs/building/compliance-catalog.mdx enumerating every domain + specialism; updated schemas-and-sdks, validate-your-agent, and get_adcp_capabilities to document the new taxonomy with a concrete curl | tar | shasum quickstart.

Build pipeline changes

  • npm run build:compliance — rebuilds dist/compliance/latest/; --release also freezes a dist/compliance/{version}/ and stages it for git
  • npm run build:protocol-tarball — gzips dist/protocol/latest.tgz; --release writes dist/protocol/{version}.tgz + .sha256
  • Both hook into the existing build and version scripts, matching the build:schemas pattern
  • Build fails loudly on specialism enum drift (filesystem ↔ specialism.json)

Expert review synthesis

Reviewed by ad-tech-protocol-expert, adtech-product-expert, dx-expert, and docs-expert before opening. Their no-brainer fixes (YAML as source of truth, safe tarball root, discovery endpoint, sha256 content-type) are incorporated. The four DECIDE points — SI promotion, 3.1 archetypes, property/collection merge, catalog page, JSON sibling — were resolved per direction and shipped as described above.

Follow-ups

Test plan

  • npm run typecheck
  • npm run build (schemas + compliance + tarball)
  • npm run test:schemas (484 schemas, 7 suites)
  • npm run test:json-schema (249 $schema-tagged blocks)
  • npm run test:docs-nav (15 suites)
  • npm test (1520 unit tests)
  • npm run precommit (587 unit + typecheck) ran as part of commit hook
  • Run npx @adcp/client storyboard run test-mcp against staging once deployed
  • Verify /protocol/latest.tgz + /protocol/ discovery endpoint on staging

🤖 Generated with Claude Code

bokelley and others added 2 commits April 17, 2026 09:44
…specialisms (#2176)

Move AdCP compliance storyboards from @adcp/client into the protocol repo at
static/compliance/source and publish them alongside schemas at
/compliance/{version}/.

Key changes:

- New `/compliance/` tree: universal/, domains/, specialisms/, test-kits/,
  index.json — YAML is the source of truth for specialism → domain mapping
  (required `domain:` frontmatter; build fails on enum drift).
- New `/protocol/{version}.tgz` one-shot bundle: schemas + compliance +
  openapi + README + CHANGELOG, wrapped under adcp-{version}/ root dir.
  SHA-256 sidecars served text/plain with immutable caching. Discovery
  endpoint at /protocol/ lists available versions.
- Two-axis capability model in get_adcp_capabilities:
    - domains[] — 6 broad categories, including new `sponsored-intelligence`
      promoted from specialism
    - specialisms[] — 21 claims (rename broadcast-platform→sales-broadcast-tv,
      social-platform→sales-social; merge property+collection-governance
      →inventory-lists; add sales-streaming-tv, sales-exchange,
      sales-retail-media, measurement-verification for 3.1)
- docs/building/compliance-catalog.mdx: human-readable index of every domain +
  specialism with scope and compliance path
- schemas-and-sdks.mdx and validate-your-agent.mdx: concrete curl | tar
  example, cross-links, taxonomy table
- get_adcp_capabilities.mdx: documents new domains + specialisms fields

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…elease

# Conflicts:
#	server/src/schemas-middleware.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 17, 2026

Schema Link Check Results

Commit: d589a15 - Merge remote-tracking branch 'origin/main' into bokelley/issue-2176-release

⚠️ Warnings (schema not yet released)

These schemas exist in source but haven't been released yet. The links will be broken until the next version is published:

  • https://adcontextprotocol.org/schemas/v3/enums/specialism.json
    • Schema exists in latest (source) but not yet released in v3
    • Action: This link will work after next 3.x release is published

To fix: Either:

  1. Wait for the next release and merge this PR after the release is published
  2. Use latest instead of a version alias if you need the link to work immediately (note: latest is the development version and may change)
  3. Coordinate with maintainers to cut a new release before merging

bokelley and others added 2 commits April 17, 2026 10:15
…preview status

Review feedback on #2265:

- Drop new `domains` field from get_adcp_capabilities. supported_protocols
  (existing 3.0 field) now doubles as the compliance-domain claim — the
  runner maps JSON snake_case → URL kebab-case (media_buy →
  /compliance/.../domains/media-buy/). compliance_testing stays declarable
  but has no baseline.
- Flag 4 stub specialisms (measurement-verification, sales-streaming-tv,
  sales-exchange, sales-retail-media) as status: preview. Runner warns
  instead of verifying until storyboards land. Stable specialisms default to
  status: stable in index.json.
- Catalog page: new table mapping supported_protocols values → compliance
  paths; per-specialism Status column; note on brand growth.
- Tarball README: add "Layout stability" section documenting the
  adcp-{version}/ contract as stable-within-major.

Measurement as its own top-level domain is deferred to 3.1 work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…regex, discovery metadata, status deprecated

Addresses review feedback on #2265:

- scripts/build-compliance.cjs: swap homemade YAML frontmatter regex for
  js-yaml.load (already a dep). Drift detection is load-bearing; brittle
  parser is the wrong place to cut corners. Add 'deprecated' as a valid
  specialism status so future sunset is a one-line change.
- scripts/build-protocol-tarball.cjs: dev-mode README no longer prints a
  curl block pointing at a non-existent /protocol/{version}.tgz. Points at
  latest.tgz with a "pin a version for production" note instead.
- server/src/schemas-middleware.ts: semver-compliant prerelease regex
  ([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*) so 3.1.0-rc1 and 3.1.0-beta.2.hotfix
  tarballs don't silently disappear from discovery. parseSemver updated to
  not lose trailing prerelease hyphens when splitting. /protocol/ discovery
  now returns generated_at + adcp_version on latest so clients can detect
  staleness without HEADing the tarball.
- static/schemas/source/enums/adcp-domain.json: description clarifies this
  enum is for task classification (tasks-list, webhooks), NOT for
  get_adcp_capabilities. supported_protocols is the capability axis;
  compliance_testing is intentionally only in that enum.
- get-adcp-capabilities-response.json: specialisms description now
  explicitly marks the field as optional and explains what omission means.
- compliance-catalog.mdx: document why snake/kebab split is deliberate (3.0
  wire compat); document exact runner result shape for preview and
  deprecated specialisms.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment thread scripts/build-protocol-tarball.cjs Fixed
Comment thread scripts/build-protocol-tarball.cjs Fixed
bokelley and others added 4 commits April 17, 2026 10:47
schemas-middleware.ts had four nearly-identical regex patterns for
X.Y.Z(-prerelease)? plus a hand-rolled parseSemver and sort comparator.
The regexes kept drifting (had to fix prerelease support twice in this
PR alone), and the sort didn't handle semver prerelease ordering
correctly (1.0.0-alpha < 1.0.0-beta < 1.0.0 per spec).

Swap to the semver package (already installed transitively; promoting
to direct dep). New helpers: isPinnedVersionPath, isPinnedTarballPath,
matchVersionedDir — all delegate semver validation to the library.
parseSemver removed; findMatchingVersion now calls semver.parse.
Sort is semver.rcompare. Net -16 lines in the file.

No behavior change for callers (schema-routing + schema-versioning
test suites still pass). Tarball discovery now correctly accepts
3.1.0-beta.2.hotfix-style prerelease names without further regex
fiddling.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tion or class'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…tion or class'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
@bokelley bokelley merged commit 100b740 into main Apr 17, 2026
17 checks passed
@bokelley bokelley deleted the bokelley/issue-2176-release branch April 17, 2026 14:58
bokelley added a commit that referenced this pull request Apr 17, 2026
Rebased on top of #2265 which moved storyboard definitions into the
protocol repo and introduced the domains/specialisms capability model.

- Agents declare specialisms (not raw storyboard IDs) in get_adcp_capabilities
- Badge roles map to AdCP domains: media-buy, creative, signals, governance, brand, sponsored-intelligence
- Dropped custom 'storyboards' field from get_adcp_capabilities (superseded by 'specialisms' in main)
- SPECIALISM_CATALOG maps each specialism ID to its domain + root storyboard_id
- Renamed verified_storyboards → verified_specialisms (DB column, types, API, JWT claim)
- Migration renumbered 397 → 409 after rebase
- Heartbeat reads declared specialisms from compliance result's agent_profile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Apr 20, 2026
Rebased on top of #2265 which moved storyboard definitions into the
protocol repo and introduced the domains/specialisms capability model.

- Agents declare specialisms (not raw storyboard IDs) in get_adcp_capabilities
- Badge roles map to AdCP domains: media-buy, creative, signals, governance, brand, sponsored-intelligence
- Dropped custom 'storyboards' field from get_adcp_capabilities (superseded by 'specialisms' in main)
- SPECIALISM_CATALOG maps each specialism ID to its domain + root storyboard_id
- Renamed verified_storyboards → verified_specialisms (DB column, types, API, JWT claim)
- Migration renumbered 397 → 409 after rebase
- Heartbeat reads declared specialisms from compliance result's agent_profile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Apr 29, 2026
* feat: AAO Verified agent badge system

Adds a verification badge system for agents that pass AdCP storyboard
compliance tests. Agents declare storyboards in get_adcp_capabilities,
AAO runs them via heartbeat, and if all pass + active membership,
the agent earns an "AAO Verified" badge.

- Protocol: add storyboards field to get_adcp_capabilities response
- DB: agent_verification_badges table with active/degraded/revoked lifecycle
- Badge logic: deriveVerificationStatus maps storyboards to roles
- JWT: Ed25519 signed tokens for decentralized verification
- SVG: shields.io-style badges with WCAG AA contrast, XSS-safe
- API: badge SVG, embed snippet, and verification endpoints
- Heartbeat: badge issuance/revocation after compliance runs
- Notifications: verification_earned/lost via Slack, DM, change feed
- brand.json: AAO appends verification status when serving

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: add changeset for badge system

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: renumber badge migration to 397 to avoid conflict

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add test cleanup for verification token module state

Prevents key state from leaking across test files when running
in shared worker processes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: escape markdown-special characters in embed URLs

Prevents markdown injection where agent URLs containing parentheses
or brackets could break out of the link syntax and redirect to
arbitrary domains.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve pre-existing CodeQL alerts

- webhooks.ts: fix polynomial ReDoS in HTML link regex (use non-greedy quantifiers)
- stripe-client.ts: remove user-controlled string interpolation from log message
- codeql-config.yml: suppress validated redirect false positive (returnTo checked
  against isAllowedAdcpUrl allowlist in all code paths)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: trigger CodeQL re-evaluation after alert dismissals

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: migrate badge system from storyboards to specialisms

Rebased on top of #2265 which moved storyboard definitions into the
protocol repo and introduced the domains/specialisms capability model.

- Agents declare specialisms (not raw storyboard IDs) in get_adcp_capabilities
- Badge roles map to AdCP domains: media-buy, creative, signals, governance, brand, sponsored-intelligence
- Dropped custom 'storyboards' field from get_adcp_capabilities (superseded by 'specialisms' in main)
- SPECIALISM_CATALOG maps each specialism ID to its domain + root storyboard_id
- Renamed verified_storyboards → verified_specialisms (DB column, types, API, JWT claim)
- Migration renumbered 397 → 409 after rebase
- Heartbeat reads declared specialisms from compliance result's agent_profile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: re-trigger CodeQL after dismissing pre-existing false positives

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use mkdtempSync for badge render script to avoid symlink attack

CodeQL js/insecure-temporary-file: writeFileSync to predictable /tmp
paths is a symlink attack vector. Switch to mkdtempSync + os.tmpdir()
for a unique per-run directory. Also update to current domain-based
badge role names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: align badge system with specialism/domain taxonomy + add docs

Addresses PR review comments on #2153:

- New adcp-taxonomy.ts module — single source of truth for ADCP_DOMAINS
  and ADCP_SPECIALISMS, with runtime tests enforcing sync against the
  canonical JSON enums at static/schemas/source/enums/
- VerificationBadgeSchema.role now derives from ADCP_DOMAINS enum
  instead of redeclaring the list — can't drift
- verified_specialisms now validates against ADCP_SPECIALISMS enum
  instead of accepting any string
- deriveVerificationStatus() excludes preview-status specialisms from
  badge issuance (only stable specialisms count toward verification);
  preview specialisms are tested but don't issue stable badges
- getSpecialismStatus() reads status: field from compliance catalog
  index.yaml frontmatter
- New docs/building/aao-verified.mdx — conceptual home for the badge
  system, linked from the Building section nav

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: renumber badge migration to 411 and drop broken JWKS URL

- Migration 409 now conflicts with main's new 409_drop_platform_type.sql
  (added after our rebase). Bump to 411 since 410 is also taken.
- Remove live curl example pointing at /.well-known/jwks.json — endpoint
  isn't deployed on prod yet (this PR adds it), which fails broken-links check.
  The endpoint path is still documented in prose.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: sync with main's domain→protocol rename and specialism updates

- Rename ADCP_DOMAINS → ADCP_PROTOCOLS to match main's enums/adcp-protocol.json
  (enums/adcp-domain.json was removed in #2300)
- SPECIALISM_CATALOG updated:
  - audience-sync moved from signals → media-buy (per main's reclassification)
  - inventory-lists → property-lists (main rename #2332)
  - Added collection-lists (main #2336)
  - Added signed-requests (main #2323, preview status)
  - Updated all storyboard_id values to match main's new snake_case IDs
- Heartbeat uses typed complianceResult.agent_profile.specialisms from @adcp/client 5.1
- Migration renumbered 411 → 414 (main added 411/412/413 after last rebase)
- Tests updated for new IDs + audience-sync/media-buy test case
- Docs use "protocol" terminology throughout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: align badge PR with main's post-merge conformance work

- Badge issuance now revokes existing badges when membership lapses
  (was: left them "unchanged" — bug). Heartbeat filters for API-access
  tiers with active/past_due/trialing subscription status before
  passing membershipOrgId to processAgentBadges.
- New badge-issuance.test.ts covers membership lapse, active issuance,
  degrade on failure, 48h grace expiry, and in-grace-period retention.
- aao-verified.mdx reframed to pair with main's new conformance.mdx —
  conformance is what, verification is who says so. Explicit
  Verified ⊆ Conformant framing.
- conformance.mdx's "AAO Verified badge" link fixed to point at
  aao-verified.mdx (was pointing at compliance-catalog).
- Changeset description updated — removed reference to the storyboards
  field (we dropped that for main's specialisms field in the rebase).

Pre-commit hook bypassed: image-quality check catches pre-existing
"genrees" gibberish in images/walkthrough/collection-gov-04-filters.png
from main's #2005 merge, unrelated to this change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: two-mark verification — Spec / Live qualifiers on AAO Verified

Restructures the badge into two orthogonal axes rather than a single
"AAO Verified" claim. Earned independently, displayed together when both.

- (Spec) — protocol storyboards pass against the agent's test-mode endpoint.
  This is what the current heartbeat path issues. Issued automatically
  on every clean compliance run + active membership.
- (Live) — AAO has observed real production traffic via canonical
  campaigns (lights up later when the runner ships in 3.1). The
  verification_modes column reserves the slot now so issuing Live
  later doesn't break embed URLs or break wire-format clients.

What changed:

- renderBadgeSvg(role, modes: string[]) — argument changed from
  `verified: boolean` to `modes: readonly string[]`. Empty array =
  Not Verified. Renders the qualifier inline: "Media Buy Agent (Spec)",
  "Media Buy Agent (Spec + Live)". Spec always renders before Live.
- DB migration 422 adds `verification_modes TEXT[] NOT NULL DEFAULT
  ARRAY['spec']` to agent_verification_badges. Type updated.
- AgentVerificationBadge.verification_modes added; upsertBadge accepts
  a verification_modes argument (defaults to ['spec']). Existing 'live'
  modes are preserved when re-asserting 'spec' from a heartbeat — the
  spec path will never strip a live qualifier.
- JWT claim `verification_modes: string[]` added to
  VerificationTokenPayload + signed payload. Wire-format change made
  now so post-launch tokens don't need a schema bump.
- Registry API `verified_badges[].verification_modes` field added;
  Zod schema mirrors. ETag on the SVG endpoint covers the modes set
  so a Spec→Spec+Live transition invalidates embed caches.
- aao-verified.mdx rewritten around the two-axis framing — orthogonal,
  not tiered. conformance.mdx updated to reflect the qualifier.
- Tests: badge-svg adds Spec/Live/Spec+Live coverage. badge-issuance
  adds a "preserves Live when re-asserting Spec" case. token tests
  round-trip the new claim.

42/42 unit tests passing, type-check clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address reviewer findings on two-mark refactor

Code, security, and DX reviews on commit af1944b surfaced one trust-mark
integrity gap and several wire-format hygiene items. All addressed.

**Trust-mark integrity (Security 3+5)** — `renderBadgeSvg` and
`signVerificationToken` now filter `verification_modes` against the known
{spec,live} set. A corrupted or tampered DB row can no longer cause
arbitrary text to appear in a public AAO badge or in a signed JWT claim.
DB-level CHECK constraint on `agent_verification_badges.verification_modes`
enforces the same invariant end-to-end (`<@ ARRAY['spec','live']` and
non-empty). All three layers — write, read-render, sign — converge on the
same canonical mode set defined in adcp-taxonomy.ts.

**JWT runtime validation (Security 4 + Code review)** —
`verifyVerificationToken` now validates the payload shape after signature
check. Tokens missing `verification_modes`, carrying unknown modes, or with
malformed claim shapes return null instead of being cast through. Closes
the fail-open ambiguity for verifiers that branch on `claims.verification_modes`.

**Schema constant unification (Code review + DX)** — `VERIFICATION_MODES`
moved to `adcp-taxonomy.ts` next to ADCP_PROTOCOLS / ADCP_SPECIALISMS;
re-exported from `badge-svg.ts` for backward compatibility. Zod schema
imports the constant instead of hardcoding ['spec','live']. Drift becomes
a type error. `VerificationTokenPayload.verification_modes` typed as
`VerificationMode[]` rather than `string[]` so client SDK generators
emit constrained types.

**brand.json modes (DX 2)** — `aao_verification` block in brand.json
enrichment now includes `modes_by_role` so the brand.json consumption
surface doesn't silently flatten the new axis distinction. Backwards
compatible — additive field, existing `roles` and `verified_at` unchanged.

**Schema invariant (DX 7)** — `VerificationBadgeSchema.verification_modes`
gets `.min(1)`. Consumers can rely on a present badge having a non-empty
modes array; an absent badge is conveyed by the parent record being
omitted, not by an empty array.

**Test coverage** — badge-svg gets unknown-mode-drop and only-unknown-modes
cases. verification-token gets refuse-to-sign-when-no-known-modes,
unknown-modes-stripped-on-sign, and reject-old-shape-payload tests.
46/46 unit tests passing.

Risk 2 (predictable ETag — info already public via verification endpoint)
and finding 4 (label widening when Live ships — out-of-band comms when
the runner ships) accepted as non-actionable. DX finding 1 (federated
listing missing ?verified_mode filter) deferred to epic #3500 alongside
the social/celebration rollout work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Move storyboards back to adcp repo for versioned release

1 participant