Skip to content

feat(verification): brand.json + /verification per-version detail (#3524 stage 5)#3604

Merged
bokelley merged 2 commits intomainfrom
bokelley/per-version-badges-stage5
Apr 30, 2026
Merged

feat(verification): brand.json + /verification per-version detail (#3524 stage 5)#3604
bokelley merged 2 commits intomainfrom
bokelley/per-version-badges-stage5

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Summary

Final stage of #3524. brand.json enrichment and the public /verification endpoint now carry per-version badge detail.

What ships

brand.json aao_verification block

```jsonc
"aao_verification": {
"verified": true,
"verified_at": "2026-04-30T...",
"badges": [
{ "role": "media-buy", "adcp_version": "3.1", "verification_modes": ["spec", "live"], "verified_at": "..." },
{ "role": "media-buy", "adcp_version": "3.0", "verification_modes": ["spec"], "verified_at": "..." }
],
"roles": ["media-buy"],
"modes_by_role": { "media-buy": ["spec", "live"] }
}
```

`badges[]` is the canonical forward-compat shape (Q6 of the resolved decisions). One entry per parallel-version badge; preserved order matches the API's version-DESC sort. Adding future axes won't change the array shape.

`roles[]` and `modes_by_role` are kept as deprecated aliases for one release. Values reflect "the current best mark" — highest-version badge per role. Clients reading them today keep working when parallel-version badges ship. Removal target: AdCP 4.0.

/verification endpoint

`GET /api/registry/agents/{url}/verification` (the decentralized public verifier surface) now includes `adcp_version` on each `badges[]` entry. Validated through the same shape regex the `/compliance` endpoint uses for defense in depth — a poisoned DB row returns `null` rather than passing through unchecked.

Live verification

Tested locally with seeded parallel-version badges:

```bash
$ curl /api/registry/agents/https%3A%2F%2Fbuyer.acme-adtech.dev/verification | jq .badges
[
{ "role": "media-buy", "adcp_version": "3.1", "verification_modes": ["spec","live"], ... },
{ "role": "media-buy", "adcp_version": "3.0", "verification_modes": ["spec"], ... }
]
```

What this PR does NOT change

  • `verified_at` semantics — still the most-recent-state-change timestamp across any badge
  • `verified` boolean — still true when any active badge exists
  • Wire format on the badge JWTs (already carries `adcp_version` via Stage 2)
  • Badge issuance, heartbeat fan-out, SVG rendering, panel UX — all upstream of brand.json enrichment

Stage tracker (closing #3524)

After this PR merges, #3524 is fully shipped. Deferred panel polish (role grouping, "show all versions" disclosure when 4+ badges, PROTOCOL_LABELS audit) tracked in #3603.

Test plan

  • 131/131 unit tests pass
  • TypeScript typecheck clean
  • Live /verification endpoint returns adcp_version on each badge with proper shape validation

Closes #3524.

🤖 Generated with Claude Code

bokelley and others added 2 commits April 29, 2026 22:08
 stage 5)

Final stage of #3524.

brand.json enrichment now includes a `badges[]` array — one entry
per (role, adcp_version) with full per-version detail. Per Q6 of
the resolved-decisions thread, this is the canonical forward-compat
shape: adding future axes to a badge won't change the array.

`roles[]` and `modes_by_role` stay as deprecated aliases for one
release. Their values reflect "the current best mark" — highest-
version badge per role. Clients reading them today keep working
when parallel-version badges ship; new clients should read
`badges[]` for the full picture. Removal target: AdCP 4.0.

The /verification endpoint (decentralized public verifier surface)
now includes adcp_version on each badge entry, validated through
the same shape regex the /compliance endpoint uses.

After this PR, #3524 is fully shipped. Deferred panel polish
(role grouping, "show all versions" disclosure) tracked in #3603.

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

- MED: brand.json adcp_version asymmetry. /verification and /compliance
  both gate adcp_version through isValidAdcpVersionShape; brand.json
  emitted it raw. Fixed — now mirrored across all three surfaces. The
  DB CHECK is the actual gate, but brand.json is the public buyer-
  facing surface, so any future code path that bypasses the CHECK
  (raw SQL backfill, restored snapshot) MUST NOT leak through.
- LOW: modes_by_role is a real footgun for buyers pinned to a
  specific AdCP version — they could read "spec+live" and infer the
  wrong contract for their version. Added a `deprecation_notice`
  field that ships alongside the data so long-tail crawlers see the
  warning without tracking release notes.

Code review:

- Schema gap: brand.json enrichment had no documented contract for
  the new badges[] array. Added an `AaoVerificationBlock` TS interface
  inline so future contributors don't drift the wire format. Strict
  type on the assignment so a missing field fails at compile time.
- Comment wording: "first row per role is the highest" was misleading
  because roles can interleave in the SQL sort. Clarified the dedupe
  invariant.
- Deprecation horizon: documented "≥6 months from this PR's merge per
  the cadence policy in #2359" alongside the "AdCP 4.0" target so
  buyer agents have a calendar reference rather than a moving version.

Code review nit on extracting enrichAgentEntries to a module-level
pure function for unit testing — agreed it's worth doing as a
follow-up but didn't want to expand this PR's blast radius. Filing
separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley force-pushed the bokelley/per-version-badges-stage5 branch from 7b696a1 to 2f05141 Compare April 30, 2026 02:08
@bokelley bokelley merged commit ad75858 into main Apr 30, 2026
13 checks passed
@bokelley bokelley deleted the bokelley/per-version-badges-stage5 branch April 30, 2026 02:11
bokelley added a commit that referenced this pull request Apr 30, 2026
The Stage 5 brand.json changeset (PR #3604) was committed with
`\`\`\`jsonc` (literal backslash-backticks) for its code fence, which
trips Mintlify's acorn parser on every CI run:

  parsing error ./.changeset/per-version-badges-stage5-brand-json.md:15:13 - Could not parse expression with acorn

Replacing the escaped fence with a plain `\`\`\`jsonc` block clears
the warning. Documentation/content unchanged.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Apr 30, 2026
Three follow-ups to #3524:

1. Docs (docs/building/aao-verified.mdx). Last updated for the
   orthogonal-axes framing in #3536; didn't mention the per-version
   model that just shipped. Added a "Per-version badges" section,
   updated the SVG/embed sections with both URL shapes (legacy
   auto-upgrade and version-pinned), added adcp_version to the JWT
   claim block with explicit verifier guidance ("verifiers MUST
   check adcp_version against the AdCP version they care about" —
   closes the cross-version replay concern from Stage 2's security
   review), and added a brand.json enrichment subsection documenting
   the badges[] array and the deprecation policy.

2. Refactor for testability. The shaping logic that builds the
   aao_verification block was a closure inside the brand.json route
   handler — unreachable from unit tests. Extracted to
   services/aao-verification-enrichment.ts as
   buildAaoVerificationBlock(badges). The route handler keeps the
   JSON traversal and assignment; the builder is pure with 14 new
   unit tests covering empty input, single-badge, multi-version
   dedupe with caller-ordering preserved, modes_by_role flattening
   (the "buyer pinned to 3.0 sees the wrong contract" footgun),
   adcp_version shape filtering (defense in depth), and the
   deprecation notice content. Code-review nit on PR #3604.

3. PROTOCOL_LABELS audit comment in dashboard-agents.html. The
   `${protocol} Agent${versionSegment}` label construction relies
   on PROTOCOL_LABELS values not ending in "Agent" — added a
   comment pinning the invariant so a future contributor adding a
   new protocol doesn't accidentally produce "Foo Agent Agent 3.1".
   DX expert nit from #3603.

No wire-format changes — brand.json output is byte-for-byte
identical to what shipped in #3604. Panel UX changes (role
grouping, "show all versions" disclosure) explicitly defer until
parallel-version badges land in production with real buyer feedback
to design against.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Apr 30, 2026
…3654)

Three follow-ups to #3524:

1. Docs (docs/building/aao-verified.mdx). Last updated for the
   orthogonal-axes framing in #3536; didn't mention the per-version
   model that just shipped. Added a "Per-version badges" section,
   updated the SVG/embed sections with both URL shapes (legacy
   auto-upgrade and version-pinned), added adcp_version to the JWT
   claim block with explicit verifier guidance ("verifiers MUST
   check adcp_version against the AdCP version they care about" —
   closes the cross-version replay concern from Stage 2's security
   review), and added a brand.json enrichment subsection documenting
   the badges[] array and the deprecation policy.

2. Refactor for testability. The shaping logic that builds the
   aao_verification block was a closure inside the brand.json route
   handler — unreachable from unit tests. Extracted to
   services/aao-verification-enrichment.ts as
   buildAaoVerificationBlock(badges). The route handler keeps the
   JSON traversal and assignment; the builder is pure with 14 new
   unit tests covering empty input, single-badge, multi-version
   dedupe with caller-ordering preserved, modes_by_role flattening
   (the "buyer pinned to 3.0 sees the wrong contract" footgun),
   adcp_version shape filtering (defense in depth), and the
   deprecation notice content. Code-review nit on PR #3604.

3. PROTOCOL_LABELS audit comment in dashboard-agents.html. The
   `${protocol} Agent${versionSegment}` label construction relies
   on PROTOCOL_LABELS values not ending in "Agent" — added a
   comment pinning the invariant so a future contributor adding a
   new protocol doesn't accidentally produce "Foo Agent Agent 3.1".
   DX expert nit from #3603.

No wire-format changes — brand.json output is byte-for-byte
identical to what shipped in #3604. Panel UX changes (role
grouping, "show all versions" disclosure) explicitly defer until
parallel-version badges land in production with real buyer feedback
to design against.

Co-authored-by: Claude Opus 4.7 (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.

AAO Verified: per-major-version badges (3.0 verified stays valid when 3.1 ships)

1 participant