Repository navigation
Docs: say that key-discovery "operator" is the signing agent's operator, never account.operator #1426
Description
Activity
- addedclaude-triagingTriage routine is actively working on this issue (1-3 min)Triage routine is actively working on this issue (1-3 min)
on Oct 6, 2026 Triage
Classification: docs
Bucket(s): docs, signing
Status: ready-to-implement
Milestone: (omitted — no version target specified)What the experts said:
- docs-expert: Proposal 1 is a blocker —
BrandJsonJwksResolverdocstring andagent-resolution-33.mdexample both omit the source ofbrand_json_url; two targeted sentences fix both entry points. Proposal 2 is a nit (migration doc already leads withJwksUriSignerKeys;BrandJsonJwksResolverdoesn't appear there). Proposal 3 is out of scope — constructor has no access toaccount.operator, false-positive risk is real. - dx-expert: Confirms blocker severity — the naming collision (
account.operatorvs. signing-agent operator) is structurally set up by the example; a coding agent or new integrator will reach foraccount.operatoras the only operator-named symbol in scope. Minimum fix: one sentence in the class docstring + one comment line above the constructor call in the doc example.
My take: The bug report is precise and confirmed: a seller misread
account.operatorfor the brand.json URL source — a natural reading of the current docs — andrequest_signature_jwks_untrustedgave no field hint. Two surgical edits close both reading entry points (IDE hover on the class + linear doc read). Proposals 2 and 3 from the issue are deferred as noted.- Edit
src/adcp/signing/brand_jwks.pyBrandJsonJwksResolverclass docstring (~line 368): add a sentence to the opening paragraph: "Obtainbrand_json_urlfrom the signing agent'sidentity.brand_json_urlcapabilities field — this is the signing agent's operator (whoever publishes the brand.json). Do not useaccount.operatorfrom the inbound request; that is the request's account-scoping field, unrelated to signing key discovery." - Edit
docs/agent-resolution-33.mdconstructor example (~line 21): add an inline comment or parenthetical to"https://operator.example.com/brand.json"clarifying thatoperator.example.comis the signing agent's operator domain (fromidentity.brand_json_url), not the request'saccount.operatorfield. - No changes needed to
docs/request-signing-migration.md— that doc already leads withJwksUriSignerKeyskeyed by agent URL and does not useBrandJsonJwksResolver. - Non-breaking: adds clarifying wording to a docstring and a doc example. No API surface, type, or behaviour changes.
pytest,ruff,mypyrequired before PR; fast-subset only (no integration tier needed for a doc-only diff).
Triaged by Claude Code. Session: https://claude.ai/code/session_016ztEeMWKstfGshSRaXGsJg
Generated by Claude Code
- docs-expert: Proposal 1 is a blocker —
- added and removedclaude-triagingTriage routine is actively working on this issue (1-3 min)Triage routine is actively working on this issue (1-3 min)
on Oct 6, 2026 bokelley commented
on Oct 6, 2026 ContributorAuthorMore actionsA second misreading from the same integration: the seller expected
keyidto carry the JWKS location (keyid="https://…/jwks.json#kid", as in some other HTTP Signatures ecosystems) and rejected our signatures because it was a barekid. In the AdCP profile,keyidonly matches akidin the signer's published JWKS (security.mdx, profile table). The verifier finds that JWKS from the signing agent it already knows, through onboarding, a registry, oridentity.brand_json_urldiscovery. The spec deliberately gives the signer no wire field to assert its own key location (see theagent_urlderivation note), because trusting a URL from the request is self-attestation.Could the verifier docs say this next to the operator callout? Something like:
keyidis a barekid; never fetch a JWKS location taken from the request. Map the counterparty agent to its JWKS withJwksUriSignerKeys/StaticSignerKeys, or discover it withverify_from_agent_url.- addedclaude-triagingTriage routine is actively working on this issue (1-3 min)Triage routine is actively working on this issue (1-3 min)
on Oct 6, 2026 Triage (updated)
Classification: docs
Bucket(s): docs, signing
Status: ready-to-implement
Milestone: (omitted)What the experts said:
- docs-expert: The
keyid-as-JWKS-URL misreading is the same class of problem as theaccount.operatorone — a developer mapping a familiar pattern from another HTTP Signatures ecosystem. The spec deliberately omits a wire field for the signer's JWKS location to prevent self-attestation; that design decision needs one sentence of explanation near both the operator callout and thekeyidfield. - dx-expert: Confirmed — the two misreadings occur at the same verifier integration point. Pairing both callouts in the same doc section (
agent-resolution-33.mdnear theBrandJsonJwksResolverexample) is more efficient than a separate note; a new integrator reading that block gets both corrections at once.
My take: The new comment from @bokelley adds a third, well-specified doc edit to this issue's existing scope. It is non-breaking, directly addresses a confirmed real-world misreading, and naturally co-locates with the two changes already in the brief. Expanding the brief accordingly.
Updated implementation scope (3 edits, all non-breaking doc additions):
src/adcp/signing/brand_jwks.py—BrandJsonJwksResolverdocstring (~line 368): add to the opening paragraph: "Obtainbrand_json_urlfrom the signing agent'sidentity.brand_json_urlcapabilities field — this is the signing agent's operator (whoever publishes the brand.json). It is neveraccount.operatorfrom the inbound request; that field is the request's account-scoping seat holder, unrelated to key discovery."docs/agent-resolution-33.md—BrandJsonJwksResolverconstructor example (~line 22–26): add a comment or parenthetical clarifying thatoperator.example.comis the signing agent's operator domain (fromidentity.brand_json_url), not the request'saccount.operator. Immediately after, or in the same callout block, add: "keyidin the AdCP HTTP Signatures profile is a barekid; it never carries a JWKS URL. The verifier locates the signer's JWKS through onboarding (JwksUriSignerKeys/StaticSignerKeys) or discovery (verify_from_agent_url) — never by fetching a URL from the request itself, which would be self-attestation."docs/agent-resolution-33.md— verifier guidance block (lines 49–55, agent-first paths): ensure theverify_from_agent_url/async_resolve_agentblock mentions thatidentity.brand_json_urlis the JWKS source and is never derived from request fields. (Likely a one-sentence addition to the existing paragraph.)
No changes to
docs/request-signing-migration.md(already leads with agent-first paths). No API, type, or behavior changes. Required checks before PR:pytest(fast subset),ruff check .,black --check ..
Triaged by Claude Code. Session: https://claude.ai/code/session_01UKt3gk9aK6zusQWosbhGCX
Generated by Claude Code
- docs-expert: The
- removedclaude-triagingTriage routine is actively working on this issue (1-3 min)Triage routine is actively working on this issue (1-3 min)
on Oct 6, 2026
Problem
The signing docs (
docs/agent-resolution-33.md,docs/request-signing-migration.md, and theBrandJsonJwksResolverdocstrings) use "operator" to mean the signing agent's operator: whoever publishes thebrand.jsonthat lists the agent and its keys. They never say this is a different thing from theoperatorin a request's account (account.operator, the natural key's agency or seat holder). The spec makes the distinction explicitly (security.mdx, "Discovering an agent's signing keys viabrand_json_url": "It isn't theoperatorin an account's natural key"). The SDK docs don't repeat it.A seller we work with built its verifier as
BrandJsonJwksResolver(f"https://{account.operator}/.well-known/brand.json", ...). It then resolved our signing key from the buyer's agency domain and rejected every request withrequest_signature_jwks_untrusted. The exampleBrandJsonJwksResolver("https://operator.example.com/brand.json", ...)makes that reading natural. It fails for every real buyer, because agency domains don't publish the buying agent's keys.Proposal
BrandJsonJwksResolverdocstring:verify_from_agent_url/async_resolve_agent, andJwksUriSignerKeys/StaticSignerKeyskeyed by agent URL for onboarded counterparties. Present directBrandJsonJwksResolverconstruction as the lower-level option.account.operatorhost, since that pairing is almost always this mistake.