Skip to content

feat(certification): S7 Brand Protocol specialist module + credential#5525

Open
bokelley wants to merge 1 commit into
mainfrom
brand-protocol-specialist-module
Open

feat(certification): S7 Brand Protocol specialist module + credential#5525
bokelley wants to merge 1 commit into
mainfrom
brand-protocol-specialist-module

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

What

Adds the AdCP Brand specialist certification — the first specialist credential for a first-class protocol domain that had none. Module S7 (track S, capstone, prereq Practitioner), the specialist_brand credential (tier 3), the adcp_specialist_brand badge, and 9 gated reasoning/hands-on criteria via the _append_criterion semantic-ID pattern (migration 514).

Why

Brand Protocol (brand.json identity, distributed publishing, brand hierarchy, verify_brand_claim, trademarks, rights) is a headline 3.1 domain with no specialist track — build-an-agent.mdx literally said "no skill today." The domain is two-speed, and so is this module:

  • Gated (stable identity/verification layer): resolving brand.json identity (public vs authorized tiers) · distributed-publishing mutual assertion (brand_refs[]house_domain) · operator authorization (authorized_operators[]) · bilateral adagents.json confirmation · verify_brand_claim trust interpretation · direction-asymmetric trust · trademark disambiguation · identity→creative-generation · the trust-knowability spine (authorship≠truth, consistency≠standing).
  • Taught, not gated (experimental): the rights lifecycle (get_rights/acquire_rights/update_rights, brand.rights_lifecycle).

It's pitched at brand-judgment altitude, not cryptography — the signature-verification mechanics (key resolution, canonicalization, signature checking) are deferred to the S6 Security specialist; here the gate is knowing what a verification outcome means for trust.

The sandbox fixture (was missing)

The training agent's brand tenant did not serve verify_brand_claim. This builds it:

  • verify_brand_claim / verify_brand_claims on the brand tenant, returning a payload-envelope JWS signed_response (per response-payload-jws-envelope.json) under a new adcp_use: response-signing key published in the aggregated JWKS, anchored to the verification-walkthrough entities (Sportshaus / StreamHaus / Northwind).
  • authorized_operators[] added to the served brand.json + the Sportshaus walkthrough fixture so operator authorization is demonstrable.
  • The bulk variant emits schema-conformant result_entry shapes (status on success, {error} on failure).

Also

  • docs/learning/specialist/brand.mdx + overview.mdx row + docs.json nav.
  • Two global Sage teaching-quality fixes (no meta-narration; affirmation on stacked corrections) + a capstone brevity guardrail in certification-tools.ts.

Validation

  • Migration applies + idempotent (513 collection-catalog + 514 brand apply in sequence); typecheck clean; full pre-commit suite green (977 root + 4164 server tests); docs broken-links + nav green.
  • Two blind-graded persona runs (7 personas × 2 graders): discrimination correct, teach-to-test mimicry caught, confident-wrong sunk by the trust floor, experimental-rights backdoor closed.
  • Sage teaching simulation across 5 experience personas: the brand-judgment altitude lands for all (including non-technical), crypto rabbit hole never fired.
  • 5-domain expert review (security/code/protocol/education/docs): all blockers + majors addressed (the rights-gating blocker, two bulk-schema wire-shape bugs, the intro wording, broken link).

Not for auto-merge

Reviewable diff; needs a human/second-reviewer pass before merge.

🤖 Generated with Claude Code

…tial

Adds the AdCP Brand specialist credential — module S7 (track S, capstone,
prereq Practitioner), the specialist_brand credential (tier 3), the
adcp_specialist_brand badge, and 9 reasoning/hands-on gated criteria via the
_append_criterion semantic-ID pattern (migration 514).

Brand Protocol had no specialist credential despite being a first-class
domain. Two-speed maturity: gates the stable identity/verification layer
(brand.json resolution, brand_refs[]<->house_domain reciprocity,
authorized_operators[] operator authorization, bilateral adagents.json,
verify_brand_claim trust interpretation, trademark disambiguation,
identity->creative-generation, and the trust-knowability spine); teaches the
experimental rights lifecycle without gating it.

The sandbox brand tenant did not serve verify_brand_claim, so this builds the
fixture: verify_brand_claim/verify_brand_claims on the brand tenant returning
a payload-envelope JWS signed_response (new response-signing key published in
the aggregated JWKS), anchored to the verification-walkthrough entities; plus
authorized_operators[] on the served brand.json + Sportshaus fixture. The
credential is pitched at brand-judgment altitude — the cryptographic
verification mechanics are deferred to the S6 Security specialist.

Docs: docs/learning/specialist/brand.mdx + overview.mdx + docs.json nav.
Sage: two global teaching-quality fixes (no meta-narration; affirmation on
stacked corrections) + a capstone brevity guardrail.

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

@aao-release-bot aao-release-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean module. The protocol surface conforms and the experimental rights lifecycle is fenced off from the gate in all three places it could leak. Holding the stamp because you marked it not-for-auto-merge — this is the second-reviewer pass, not the merge gate.

Things I checked

  • JWS envelope conforms. ad-tech-protocol-expert: sound-with-caveats. The signed response member folds sandbox:true into both the unsigned body and payload.response (brand-claim-handlers.ts:909, :965), so the body==payload.response invariant holds — locked by the single-target test at training-agent-brand-claim-signing.test.ts:1368-1371. response is additionalProperties:true, so the marker validates.
  • The bulk shaping handles the schema's subtlest trap. verify-brand-claims-response.json requires status on the success arm (not verification_status) and forbids status/claim_type on the error arm. The handler remaps verification_status->status and emits {error}-only failures (brand-claim-handlers.ts:953-959). Right shape. The single-target arm correctly keeps verification_status.
  • Migration 514 is idempotent and converges to exactly 9 criteria. The INSERT ... ON CONFLICT DO UPDATE resets exercise_definitions to the base (empty success_criteria) on every re-run (514:419-424), then the nine _append_criterion calls re-append from zero with an already_present guard — no double-count. FK order is correct (module -> badge -> credential), and the define/call/DROP FUNCTION pattern matches prior migrations. 514 is the next free number after 513.
  • No key leakage, cross-purpose isolation holds. security-reviewer: no production-impacting issues. brand-response-signing.ts destructures only {kty,crv,x} from the public export — no path for d to reach the JWKS — and the brand key carries a distinct adcp_use: response-signing + training-brand-resp- kid, asserted at training-agent-brand-claim-signing.test.ts:1341-1351. The ADCP_AUTH_TOKEN in brand.mdx:226 is the pre-existing public sandbox token, not a new leak.
  • Curriculum alignment is clean. education-expert: sound-with-caveats. Nine gated criteria map onto the four assessment dimensions with no orphans and nothing double-gated; the experimental rights lifecycle is taught-not-gated in all three places (empty inline success_criteria, no _append_criterion for rights, explicit "not assessed" prose). Stable semantic criterion IDs — the right shape for re-cert triggering.

Follow-ups (non-blocking — file as issues)

  • Bulk brand_domain can mix subjects. brand-claim-handlers.ts:962-963 picks brand_domain from the first resolved claim, but subsidiary claims resolve to sportshaus-holdings.example while parent/property/trademark resolve to streamhaus.example. A mixed batch signs one envelope whose brand_domain doesn't describe every results[] entry. Spec-legal (bulk is intra-agent, one brand_domain per envelope) and intentional per the "one tenant answers for several houses" header note — but it's a fixture smell that would be wrong if one agent ever mapped to one brand. Worth a comment at minimum.
  • expected_resolution_window_days emitted at the public tier. The spec text is self-contradictory (verify_brand_claim.mdx:76/:133: REQUIRED on pending_review yet listed Authorized-only). The handler always emits it (brand-claim-handlers.ts:679), which satisfies the MUST but exposes an authorized-tier field name to unauthenticated callers. Flag for the spec owner, not this PR.
  • Criterion 8 vs its weight (education-expert). s7_ex1_sc_identity_drives_generation is the most conceptually loaded criterion but shares the lowest-weighted 20% dimension with identity resolution — a learner can fail generation-mapping entirely and still clear the dimension. Either split the dimension or down-scope the criterion text.

Minor nits (non-blocking)

  1. Real brand name in a new fixture. brand-claim-handlers.ts:697 uses nikeinc.example ("Nike") as the disputed parent claim. CLAUDE.md forbids real company names in new examples — use a fictional house (apex-holdings.example). Mitigating: static/schemas/source/brand.json already ships nikeinc.com, so it's not novel in the repo, but it's net-new here and a one-token fix.
  2. Criterion 5 wording straddles the S6 boundary (education-expert). s7_ex1_sc_signed_response_trust_interpretation asks the learner to reject "a response whose signed payload does not match" — detecting a mismatch is the S6 cryptography the module disclaims. The scoring guide already softens this to "knowing the response must be verified is required"; align the criterion text to match so the gated/not-gated seam stays clean.
  3. Bulk invariant untested. The single-target test asserts body==payload.response; the bulk test (training-agent-brand-claim-signing.test.ts:1423-1432) only checks envelope verification and count. Construction is identical, but one expect(signed_response.payload.response).toEqual({results, sandbox:true}) would lock it.

Code is clean and CI-green per the PR body. Flipping to approve needs only the human merge gate you asked for plus the nikeinc.example rename.


Held for human approval: PR body marks this "Not for auto-merge — needs a human/second-reviewer pass before merge." No blocking label set; respecting the author's stated merge gate.

@mintlify

mintlify Bot commented Jun 14, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
adcp 🟢 Ready View Preview Jun 14, 2026, 3:24 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

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.

1 participant