Skip to content

CI run for adcontextprotocol/adcp-client-python#1537 - #2

Closed
KonstantinMirin wants to merge 51 commits into
conductor/reporting-webhook-activity-1168bfrom
fix/wba-standard-interop
Closed

KonstantinMirin wants to merge 51 commits into
conductor/reporting-webhook-activity-1168bfrom
fix/wba-standard-interop

Conversation

@KonstantinMirin

Copy link
Copy Markdown
Owner

This PR exists only to run CI for adcontextprotocol#1537 in this fork. Its base is adcontextprotocol#1523's head, under a branch name that ci.yml triggers on, because upstream CI doesn't run on adcontextprotocol#1537's base. Don't merge it.

bokelley and others added 30 commits October 15, 2026 18:41
Update the experimental AdCP 3.3 identity preview schemas from the open spec
branches, with provenance in UPSTREAM.json:

- relationship.status uses pending_approval from its own enum, and
  relationship is emitted on get_principal only when served at 3.3 or later.
- The buyer onboarding posture lives only in trust.json, at
  profiles.adcp.onboarding on the record and on an agent entry; sellers and
  buyers share one mode enum.
- The agent_identity requirement kind and documentation_url are gone, and
  aao_verified uses verified_specialisms.

Regenerate the bundle and the preview models, and export
PrincipalRelationshipStatus in place of BuyerOnboardingMode.

Refs adcontextprotocol#1500

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…final spec

Bring the experimental AdCP 3.3 agent identity up to date with the final spec
decisions and with the Go and TypeScript SDKs.

Signature path, per DR-0023 and WBA 5.5/6.10:
- Answer with WBA statuses and headers only, with one generic AUTH_INVALID
  body and never SERVICE_UNAVAILABLE. A revoked key is 403 without
  Accept-Signature.
- Key-directory discovery failures never evict a cached directory, which keeps
  verifying up to max_stale_seconds; with nothing cached the request is
  unverified: 403 with Accept-Signature by default, or anonymous via
  wba_discovery_failure, except operations in required_for.
- verify_wba_request needs only the directory and replay store. Revocation,
  bindings and grants are the authorization layer; replay and revocation store
  failures are application-level SERVICE_UNAVAILABLE.

Authorization layer:
- OriginRevocationChecker.status reports revoked, current, no_list, stale or
  unavailable. No list means identity-established and unbindable; a stale list
  fails spend closed.
- An unbound caller outside unbound_for gets what an anonymous caller gets.
  A delisted bound origin falls back to identity-established with no
  task-control exception, keeps its binding against downgrade, and cannot be
  bound again while unlisted.
- Trust records: the last record read stays authoritative; a 404 or invalid
  record after a read is an outage, never a removal; spend gets
  SERVICE_UNAVAILABLE and non-spend continues within a configurable bound.
  A grantor that served 200 never falls back to authorized_operators and an
  outage is never grant_absent.
- Grants are read from the house record, with registrable-domain comparison,
  self-operated brands, narrowing and window intersection.

Interop: 64-byte nonces, the target URI as received, unsigned JWKS directories
by thumbprint, RSA-PSS-SHA512, and the vendored Cloudflare interop fixtures.

Hardening: per owner/host trust budgets with a separate foreign-hop budget,
PSL PRIVATE-aware per-operator history quotas, backdated directory created,
replay at exactly expires plus skew, and fail-closed account claims.

Refs adcontextprotocol#1500

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rewrite the AdCP 3.3 agent identity migration guide for the final spec
decisions: errors split by layer, discovery failures, revocation states,
delisting and outage continuation, house-record grants, interop details, and
the deliberate differences from the Go and TypeScript SDKs.

Refs adcontextprotocol#1500

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

- A reused WBA nonce is 429 without Accept-Signature, as the spec requires;
  replay_status=403 only reproduces the older #7894 vector. The signer
  enforces 64-byte nonces too.
- Spend classification fails closed: update_media_buy and control_media_buy
  commit spend unless every change is a rename, pause or cancellation.
- Grantees compare as registrable domains, and the house resolver can map
  the brand to its child-brand id in the house portfolio.
- Unsigned requests to required operations get the WBA Accept-Signature
  challenge, and a web-bot-auth-only verifier answers them and RFC 9421
  signatures with 403.
- authorities refuses signatures captured for another host, with a boot
  warning when unset.
- Revocation list state is an LRU cache with revoked facts kept separately,
  so a flood of origins cannot lock new buyers out; concurrent overflow is
  unavailable. Trust history admits up to 65536 operators.
- Seller-side claim failures are PERMISSION_DENIED without details, and
  grant_absent is reserved for uncovered claims.
- Document the breaking experimental API changes, bearer fallback for
  signed callers, and the missing durable history seams.

Refs adcontextprotocol#1500

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Compare the admitted origins as an ordered list rather than by substring,
which CodeQL reads as URL sanitization.

Refs adcontextprotocol#1500

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment the expected decode failures in valid_wba_nonce and give the
revocation status protocol an explicit body.

Refs adcontextprotocol#1500

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The verifier accepts 22 to 256 character nonces again: 64 bytes is
  RECOMMENDED for signers and httpsig-02 adds no requirement. The signer
  still sends 64 bytes by default and accepts the same range from callers.
- Binding, trust-record and spend logic move out of the transport middleware
  behind one injected WbaAuthorizer; OriginBindingGate is the default,
  derived from the config so dataclasses.replace rebuilds it.
- The RFC 9421 DeprecationWarning fires only once the SDK advertises 3.3.
- Snapshot adcp.signing and adcp.types.experimental_identity in the public
  API snapshot, with the PgReplayStore stub resolving like the real class.

Refs adcontextprotocol#1511, adcontextprotocol#1500

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d libraries

The SDK keeps AdCP domain rules and takes these protocols from libraries:

- jws.py verifies with joserfc. The AdCP profile stays on top: the alg
  allow-list, exact typ, no crit, a protected kid, strict JSON in header
  and payload (joserfc keeps the last duplicate key), EdDSA pinned to
  Ed25519, and the fetcher's own size cap instead of joserfc's defaults.
- keygen.py generates keys and exports public JWKs with joserfc; the
  curve policy, AdCP JWK members and the CLI stay in the SDK.
- standard_webhooks.py signs with the specification's standardwebhooks
  package. Verification calls Webhook.sign and matches v1 tokens in
  constant time, because Webhook.verify reads the wall clock with a fixed
  window and cannot honor the caller's now and tolerance.

Adds joserfc and standardwebhooks, and raises the cryptography floor to
45.0.1, which joserfc requires.

Refs adcontextprotocol#1511, adcontextprotocol#1500

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… Auth with adcp#7894

Moves every RFC 9421 layer onto http-message-signatures 2.0.1 and RFC 8941
onto http-sf, replacing the archived http-sfv:

- adcp.signing._rfc9421 holds the component resolver that supplies the
  ;key and ;req parameters (pyauth/http-message-signatures#29) and the
  AdCP @target-uri/@authority canonicalization, plus thin sign/verify
  wrappers over HTTPMessageSigner and HTTPMessageVerifier.
- The 3.2 request and webhook profiles verify at step 10 through
  HTTPMessageVerifier and sign through HTTPMessageSigner.sign; the async
  SigningProvider path takes the library's own base. Error codes and step
  order are unchanged; every conformance vector passes.
- crypto.py dispatch uses the library algorithm classes; Signature,
  Signature-Input and Content-Digest parse with http-sf, which refuses
  duplicate keys and parameters through on_duplicate_key.
- Governance JWS signs and verifies with joserfc; publisher-pin
  thumbprints use joserfc; JWS splitting uses joserfc extract_compact.

Aligns the experimental Web Bot Auth profile with the final
adcontextprotocol/adcp#7894 at e24eeb09: one signature and one
Signature-Agent member per request, signed directories only, ed25519 and
rsa-pss-sha512 only, a 64-byte nonce, and 429 with Accept-Signature
carrying a fresh nonce for a replay. Re-vendors the wba-profile vectors,
governance vectors included, with provenance.

Refs adcontextprotocol#1511

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Read one header spelling everywhere: the library request takes the first
  spelling of a name, as the profile lookups do, and the step-1 precheck
  refuses a signed field that a mapping carries under two spellings.
- Refuse parameters on a Signature member and a req parameter that is not
  a true flag.
- verify_wba_governance_issuer registers the action-binding crit markers,
  checks marker/claim pairing, and checks the issuer before key discovery.
- Typed duplicate-field errors, an explicit window guard on the shared
  verify helper, a tab-tolerant profile-selection walk, and contract tests
  for the http-sf primitives it uses.

Refs adcontextprotocol#1511

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add tests/wba_interop/adapter.py, which answers the four operations of
the AdCP interop harness (sign_request, sign_directory, verify_request,
and verify_directory) through the public adcp.signing API.

Add a wba-interop CI job. It checks out tests/wba-interop and the
wba-profile vectors from adcontextprotocol/adcp at ADCP_REF, the head of
the harness pull request (adcontextprotocol/adcp#8298), installs the
harness with npm ci on Node 24, and runs run.mjs against the adapter.
Apply the Web Bot Auth rules that the Cloudflare web-bot-auth reference
and the profile state:

- Sign and verify with ed25519 only. RSA-PSS leaves the WBA signer,
  verifier, and directory, and the dead RSA branch of the governance
  issuer check goes with it. ES256 stays on the AdCP RFC 9421 profile.
- Refuse a signature that has no alg parameter. The verifier no longer
  infers alg from the key.
- Ignore a directory key the profile can't use, and the signature that key
  makes (RFC 7517 section 5). Refuse a directory in which a usable key
  doesn't sign (WBA Appendix B.1, one signature per key).
- Refuse a directory served with any media type other than
  application/http-message-signatures-directory+json (WBA section 5.5.1).
b64url_decode used the standard library's lenient decoder, which drops
characters outside the alphabet and ignores padding and trailing bits.
Verifier step 8 decodes the signer's JWK through public_key_from_jwk, so
it accepted a JWK whose x member had "!!" inserted.

Build b64url_decode and b64url_encode on joserfc.util, and import Ed25519
and P-256 JWKs with joserfc's OKPKey and ECKey through one helper,
crypto.import_jwk. The JWS and governance paths use the same helper and
decoder instead of their own copies. The SDK keeps one check that joserfc
leaves open: a P-256 member is exactly 32 bytes (RFC 7518 section
6.2.1.2).
Remove hand-written code that joserfc, http-sf, and
http-message-signatures already provide:

- public_directory_key exports the JWK with joserfc's
  OKPKey.as_dict and thumbprint.
- jwk_thumbprint passes joserfc the members that the key type's registry
  marks as required, instead of its own RFC 7638 member table. It
  returns the base64url thumbprint, so thumbprint_key_id no longer
  decodes and re-encodes it.
- A directory key's x member is checked by importing it with joserfc,
  and the nonce check decodes base64url strictly, so neither re-encodes
  a value to compare it.
- The async signer and the verifier build Signature-Input with http-sf
  (signature_input_field) instead of an f-string.
- sign_key_directory adds each key's signature to the same message with
  HTTPMessageSigner.sign(append_if_signature_exists=True) instead of
  joining the fields with ", ".
Under -00 section 5.2.1, a Signature-Agent member with type=directory
names a key directory, and the verifier ignores a member with any other
type (profile rule 6). The verifier already reads type=directory as a
Token and as a String, as Cloudflare's parser does, and ignores jwks_uri
and cimd, which leaves the signature without a member keyed to its label
(403, harness scenarios R18 and R18b). No test covered either spelling.

The nonce test's signing moves into a helper, _sign_vector_request, that
signs the positive vector's request with the library, so a test can sign
a Signature-Agent shape the SDK signer never emits.
RFC 9421 sections 4.1 and 4.2 define every Signature-Input member as an
inner list and every Signature member as a Byte Sequence with no
parameters. The verifier checked this only for the web-bot-auth member,
so a sibling such as cdn=token verified. Cloudflare's http-message-sig
refuses such a field as MalformedField, which the profile answers with
400 (harness scenario R20b).

_check_signature_members runs on both fields before a member is
selected, for requests and for key directories, so a malformed
web-bot-auth member is 400 too. A member with another tag is still not
read otherwise. _verify_crypto no longer checks its own member.
The Signature-Agent member value is the signer's https origin, and the
profile compares it as a string: a lowercase host, no default port, no
trailing slash, and no path, query, fragment, or userinfo. The verifier
canonicalized the value first, so https://BUYER.example.com:443/agents/a
verified as https://buyer.example.com.

The verifier now refuses any value that differs from its canonical origin
with 403, before key discovery (harness scenarios R17, R17c, R17d, and
R17e). http:// and userinfo were already refused. sign_wba_request
already sends the canonical origin, and a test pins it.
The governance vectors leave the Web Bot Auth profile's vectors, and the
profile drops its governance checks: verify_wba_governance_issuer is an
AdCP-layer check that runs after WBA verification. The function stays.

Move the three governance vectors, with the e24eeb09 keys file they sign
with, to vectors/wba-governance, with their own provenance record. Move
their tests from test_wba_profile.py to test_wba_governance.py, which
shares the profile tests' directory resolver.
Re-vendor static/compliance/source/test-vectors/wba-profile from
adcontextprotocol/adcp at 49b3a315, the head of #7894. The request and
directory vectors keep their signatures and change only their comments.
The directory no longer carries governance vectors or the governance
key, and it adds scenarios.json, the failure scenarios that the interop
harness signs at run time.
…hook

ProfileSigner reaches HTTPMessageSigner's protected base builder because the library has no public way to get a signature base before signing. pyauth/http-message-signatures#31 asks for one, so the module and the class docstrings name it as the condition for dropping the hook.
…ed helpers

The governance verifier had its own compact JWS splitter, its own strict
JSON parser, and two checks that decoded base64url and re-encoded it to
refuse a non-canonical spelling.

Split the token with jws.parse_compact_jws and parse each segment with
parse_strict_json, the splitter and parser that the revocation-list and
Web Bot Auth paths use. b64url_decode is strict, so the re-encode checks
go too.

Outcomes and error codes are unchanged, with two exceptions where the
old parser was weaker than every other JWS path in the SDK: a segment
with an unpaired surrogate escape is refused (governance_token_invalid),
and nesting deep enough to raise RecursionError is governance_token_invalid
instead of an uncaught exception. Some failure messages change, because
the splitter refuses a malformed header before the JSON step. The
governance authorization tests (tests/test_governance_authorization.py
and tests/conformance/vectors/governance-authorization.json) gate it.
The governance verifier and the Web Bot Auth governance issuer check each
carried a copy of security.mdx checklist step 4, and each checked the
crit syntax by hand.

Add governance.critical_claims_failure and call it from both. It hands
the crit list and the markers to joserfc's JWSRegistry.check_header,
which refuses a crit that isn't a list of registered names and a marker
that is missing or isn't a boolean. The helper keeps the rules joserfc
leaves open: no repeated entry, a claim present exactly when its marker
is true, and authorized_task with authorized_payload_hash. The 3.2
verifier still requires crit, and the WBA check still treats an absent
crit as empty.

Behavior change in the 3.2 governance path, and in the WBA issuer check:
a crit entry whose marker is absent, or isn't a boolean, is now refused
(governance_token_invalid) even when its claim is absent too. RFC 7515
section 4.1.11 forbids a crit name that doesn't occur in the header, so
refusing it is correct. Every other outcome is unchanged. The governance
authorization tests (tests/test_governance_authorization.py and
tests/conformance/vectors/governance-authorization.json) and
tests/conformance/signing/test_wba_governance.py gate it.
The Web Bot Auth governance issuer check built its own joserfc registry,
a second copy of governance._governance_jws_registry with its own
algorithm table. Use the governance registry for both verifiers, and let
_AdcpJwsRegistry know RFC 9864's Ed25519 name, which a Web Bot Auth
governance token may carry.

Behavior is unchanged. Each caller still narrows the registry to its own
algorithms: the revocation-list paths to EdDSA and ES256, the 3.2
governance verifier to the token's EdDSA or ES256, and the WBA check to
EdDSA or Ed25519 after it matched the alg to the key. The WBA table's
PS512 entry is dead: with Ed25519 keys only, no PS512 token reaches
signature verification. joserfc's extract_compact doesn't read the
registry's algorithms, so splitting is unchanged too. The governance
authorization tests, tests/conformance/signing/test_wba_governance.py,
and the JWS and revocation-list tests gate it.
crypto.import_jwk hands the whole JWK to joserfc, and joserfc derives
the public key from d when d is present. So public_key_from_jwk, the
request and webhook verifiers' step 8, and the JWS verifier used the key
that d names, not the key that x names. A kid or a publisher pin names a
key by its RFC 7638 thumbprint, which covers x (and y) only. A JWKS
entry that carried the pinned x beside another key's d passed the pin
check, and the signature that d made verified. Before adcontextprotocol#1537,
public_key_from_jwk read x alone.

import_jwk now drops the private members unless private=True, which
only private_key_from_jwk passes. The Web Bot Auth module takes the
private-member set from crypto instead of keeping its own copy.

test_public_key_is_read_from_the_public_members and
test_reject_signature_by_a_private_member_beside_the_key failed before
this change.
… table

Commit 3f0c801 rewrapped one annotated assignment in governance.py into
a shape that black, the formatter that .pre-commit-config.yaml runs at
24.10.0, refuses. This puts back the line as black writes it, which is the
line on the base branch.
…s claim together

security.mdx checklist step 4 (AdCP 3.2.1 to 3.2.3) requires "each
boolean protected-header marker, its crit entry, and its payload claim to
appear together", and the unknown-extension rule says verifiers "MUST also
reject when the critical marker and its corresponding claim do not appear
together". critical_claims_failure compared the claim only against a crit
entry with a true marker, so it accepted a marker for a claim the payload
doesn't carry: a crit entry with a false marker, or a marker with no crit
entry. Both governance verifiers share the helper.

The helper now requires the three to be present together, with the marker
true. test_critical_marker_crit_entry_and_claim_appear_together failed
before this change. A repeated crit entry with its marker present is now
a case of test_critical_claims_follow_rfc_7515_crit_rules, so the
duplicate check has a case that only it refuses.
With Web Bot Auth on ed25519 only, nothing reaches the rsa-pss-sha512
entry of _rfc9421.ALGORITHMS. The AdCP request and webhook profiles check
alg against ALLOWED_ALGS (ed25519 and ecdsa-p256-sha256) before the table,
and the Web Bot Auth signer and verifier take ed25519 from the key. The
table and the module text now name the two algorithms the SDK signs and
verifies with.
…head

adcontextprotocol/adcp#7894 moved from 49b3a315 to 2546cf5f, which adds
scenarios R18c and R18d: a Signature-Agent member with type=directory as
a Token and as a String verifies. Only scenarios.json changes; the request
and directory vectors are byte-identical. The provenance record, the
vectors README, and the provenance test name the new commit.

docs/agent-identity-3-3-migration.md still named e24eeb09, the commit
before 49b3a315, as the profile it follows; it names 2546cf5f now.
adcontextprotocol/adcp#8298 was force-pushed: its head is 6bf069b6, and
a7708e41, the commit ADCP_REF named, is no longer on the branch. A commit
no ref reaches can stop being fetchable. The new head also grades
scenarios R18c and R18d, type=directory as a Token and as a String, which
raises the check count from 52 to 54. The SDK adapter passes all 54 at
this commit.
verify_wba_governance_issuer screened the token's alg against a literal
set that still held PS512 after the profile went to Ed25519 only. A PS512
token passed the screen, made the verifier fetch the issuer's key
directory, and failed only at the key-to-alg match. The screen now reads
the profile's own table, _GOVERNANCE_JWS_ALGS, so the token is refused
with governance_token_invalid before any fetch.
test_governance_alg_outside_the_profile_is_refused_before_key_discovery
failed before this change.
…token_invalid

verify_wba_governance_issuer tested the header's alg for membership in a
set. A JSON list or object there is unhashable, so the verifier raised
TypeError instead of refusing the token. It now refuses an alg that isn't
a string as governance_token_invalid, the code the other malformed-header
cases get. test_a_non_string_governance_alg_is_an_invalid_token failed
before this change. The 3.2 governance verifier gets the same fix on main.
…litter

Commit 4e07819 moved the AdCP 3.2 governance verifier onto
jws.parse_compact_jws and strict segment parsing, but
verify_wba_governance_issuer kept its own sequence: joserfc's
extract_compact, a base64url decode of the header segment, and two
parse_strict_json calls with a separate object check. It now calls
parse_compact_jws and governance._decode_json_segment, as the 3.2 verifier
does.

Every outcome and error code is unchanged. A token whose header or claims
is valid JSON but not an object now gets the reason "governance token is
not a compact JWS" instead of its own reason; both are
governance_token_invalid. tests/conformance/signing/test_wba_governance.py
gates it.
…directory

Commit d4ca112 moved the vectors of verify_wba_governance_issuer out of
the wba-profile vectors into tests/conformance/vectors/wba-governance/.
The migration guide still said the governance/ vectors grade the check.
_parse_directory_keys refuses an entry that carries d, p, q, dp, dq, qi,
k, or oth before it skips the keys the profile can't use. Only an oct key
with k graded that order. The new test publishes a P-256, an RSA, and an
Ed25519 key with its private members beside a usable Ed25519 key that
signs, and expects "private key material" for each. If the scan moved
after the skip, each directory would verify on the usable key, and all
three cases fail, as moving the check showed.

_directory_signed_by takes published_private, the signers it lists with
their private members.
…at f311e1b5

adcontextprotocol/adcp#8298 moved to ef966de1 and #7894 to f311e1b5.
ADCP_REF names the new harness commit. The wba-profile vectors are
vendored from f311e1b5: scenarios.json and the upstream README change,
and the request and directory vectors are byte-identical. The provenance
record, the vectors README, the provenance test, and the migration guide
name the new commit.

The new scenarios include D14 and D14b: a directory in which an extra
P-256 key, or the signing Ed25519 key, carries its private d is refused.
The profile at f311e1b5 also records a nonce only after the request
verifies; verify_wba_request claims the nonce after the signature
verifies. The SDK adapter passes all 63 checks at ef966de1.
fetch_key_directory sent "Accept: application/http-message-signatures-
directory+json, application/jwk-set+json;q=0.9, application/json;q=0.8",
while _verify_directory accepts only the directory media type (WBA
section 5.5.1). A server that negotiates content could answer with
application/json, which the verifier then refuses. The request now
offers DIRECTORY_MEDIA_TYPE alone.
test_directory_fetch_asks_for_the_directory_media_type_only failed
before this change.
private_key_from_jwk imported the JWK with joserfc, which requires x.
A d-only JWK, which WebhookSender.from_jwk took before adcontextprotocol#1537, raised
ValueError, and a JWK that paired d with another key's x loaded without
complaint: the sender then signed with d while its JWK named x's key,
and only the receiver noticed.

The function now builds the key from d alone (strictly decoded, 32
bytes) and treats the public members as optional. When x (or y) is
present, it must be the public key of d, or ValueError is raised at load
time. test_a_private_jwk_needs_only_d and the Ed25519 case of
test_a_private_jwk_whose_public_members_name_another_key_is_refused
failed before this change; a mismatched P-256 pair was already refused.
… only

OriginRevocationChecker built its AsyncCachingRevocationChecker without
an algorithm list, so averify_jws_document fell back to ALLOWED_JWS_ALGS
(EdDSA and ES256). Directory keys are Ed25519 only and joserfc refuses
ES256 with an OKP key, so no ES256 list verified, but the verifier still
resolved the list's key from the origin's directory before refusing it.
The profile's revocation-list JWS is EdDSA only, as the #7894 answer for
Python stated.

AsyncCachingRevocationChecker and its from_issuer_origin take
allowed_algs, defaulting to ALLOWED_JWS_ALGS, and pass it to
averify_jws_document. OriginRevocationChecker passes {"EdDSA"}, so a list
with any other alg is refused before key resolution.
test_a_revocation_list_is_eddsa_only failed for ES256 before this change.
…er case

The #8298 D14c case publishes a P-256 key's private scalar as "D". The
private-member scan lower-cases each member name, so the SDK already
refuses it, but no in-repo test spelled a member in upper case.
test_a_private_member_refuses_the_directory_whatever_its_case covers D,
P, and Q. It fails when the scan compares names case-sensitively.
…efusal

The adapter turned every exception into {"ok": false}, and the harness
grades a directory scenario with no expected status as passed when ok is
false. A TypeError in the SDK therefore graded as a correct refusal on
D08 to D14c.

The adapter now reports only the SDK's refusals: WbaVerificationError,
and the ValueError it raises when it refuses to sign
(WbaVerificationError subclasses ValueError). Any other exception
propagates and the adapter exits with a traceback.
test_the_interop_adapter_reports_only_sdk_refusals failed for TypeError
and KeyError before this change.
adcontextprotocol#1537 changes four exported adcp.signing functions that also exist on
main 9.x. docs/request-signing-migration.md section 7 lists each change
and what an adopter checks before upgrading.

BREAKING CHANGE: adcp.signing.b64url_decode accepts only RFC 7515
base64url (no padding, no standard-alphabet characters, no non-zero
trailing bits) and raises ValueError for anything else.
public_key_from_jwk reads a JWK through joserfc, so its ValueError text
changes. private_key_from_jwk raises ValueError when a JWK's x (or y)
isn't the public key of its d. adcp.signing.publisher_pins.jwk_thumbprint
returns the RFC 7638 thumbprint as a base64url str instead of the digest
bytes.
…only

The migration guide now says that Web Bot Auth signs with ed25519
following the current #7894 page (5c3876073f). The working group has
not ratified that page, so a reversal there restores the removed
algorithm here too.
…K's resolvers

http-message-signatures 2.1.0 resolves the RFC 9421 ;key and ;req
component parameters (pyauth/http-message-signatures#30) and adds
HTTPMessageSigner.prepare and apply for a signature produced elsewhere
(adcontextprotocol#31). It also replaced the resolver's url attribute with url_parts, so
the SDK's resolver subclasses, which read url, failed every signature
base under 2.1.

The SDK now uses the library for what the subclasses did:

- Every profile resolves components with HTTPSignatureComponentResolver.
  ComponentResolver (the ;key and ;req branches, a derived-component
  allow-list, and its own trimming and control-character checks),
  ComponentError, AdcpComponentResolver, and Web Bot Auth's
  _WbaComponentResolver are removed.
- The AdCP RFC 9421 profile hands the library a request whose URL is the
  canonical target URI (canonical.signing_message), so the library derives
  the AdCP @target-uri and @authority from it. Web Bot Auth hands it the
  target URI as received.
- The async signer uses prepare and apply instead of the protected base
  hook.

build_signature_base still builds a base through the protected
_build_signature_base: the library has no public call for a base over
parameters the caller orders.

The library is the reference for every observable change, and each one
follows it:

- A covered derived component beyond @method, @target-uri, and
  @authority, such as @path, resolves and verifies instead of being
  refused. Neither profile refuses extra components.
  test_an_extra_covered_derived_component_verifies replaces
  test_unsupported_derived_component_wraps_as_header_malformed.
- A Web Bot Auth @target-uri for a URL with no path ends in "/", and one
  with a fragment or userinfo drops them. @authority strips a zero-padded
  default port.
- A covered header's obsolete line folding is unfolded (RFC 9421 section
  2.1) instead of refused.
- A lowercase method, a malformed percent escape, and a raw "{", "}", or
  "|" in the URL, which RFC 3986 doesn't allow, are refused. The signers'
  ValueError says to percent-encode the URL.
- A Web Bot Auth target URI that isn't a valid URI is 403, the profile's
  status for a failed verification, instead of 400.

Tests that asserted on the removed internals are rewritten against the
library: test_rfc9421_library_contract.py drops four tests and adds
test_library_resolves_key_and_req and
test_verify_tells_a_base_failure_from_a_bad_signature, and the WBA test
helpers sign with the library resolver.

BREAKING CHANGE: adcp.signing requires http-message-signatures 2.1.0 or
later. sign_request, async_sign_request, and sign_wba_request raise
ValueError for a lowercase method or a URL that isn't valid RFC 3986, such
as one with a raw "{", "}", or "|" in its query, and the verifiers refuse
such a request. A covered derived component beyond the required ones
verifies.
…at e5929470

ADCP_REF moves to 306cdfa6, the #8298 head, where the harness grades an
adapter crash as a failure, never as a refusal. The wba-profile vectors
move to e5929470, the #7894 head: scenarios.json adds D14c (a private
member spelled in upper case) and equals the harness's copy at 306cdfa6.
The request and directory vectors, keys.json, and the upstream README are
byte-identical to f311e1b5. With http-message-signatures 2.1.0 the
adapter passes 64 of 64 checks, with no adapter crash.
…g-storage

Ports the driver change from adcontextprotocol#1521 (bde770b) on main. The JS SDK moved
createFilesystemReportingRowObjectProviderV1 out of the ledger package, so the
row-storage interop lane fails on any branch cut before adcontextprotocol#1521. The same change
is already on main and next, so the sync merges cleanly.
bokelley and others added 6 commits October 19, 2026 14:39
…o claude/issue-1511-rfc9421-library

Brings in next through adcontextprotocol#1514's branch. The one conflict was the import
block of src/adcp/signing/verifier.py: this branch imports `_rfc9421`,
and next (adcontextprotocol#1479, AdCP 3.2.4) imports `target_uri_precheck`. The
resolution keeps both, and keeps the precheck's call site, so a
malformed wire authority is still refused as request_target_uri_malformed
at step 6 before key lookup and replay claims. A new regression test in
test_target_uri_malformed_boundary.py fails if the precheck is dropped.

http-message-signatures 2.1.0, released 2026-10-19, rewrote the
component resolver this layer subclasses (no `url` attribute). Cap the
dependency below 2.1 until the SDK moves to 2.1, which adcontextprotocol#1537 carries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…511-rfc9421-library

next's tree at 2b08ced equals adcontextprotocol#1514's head 6637374, which this branch
already merged, so the merge records the squash commit without changing
the tree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@KonstantinMirin
KonstantinMirin force-pushed the conductor/reporting-webhook-activity-1168b branch from 87f3956 to 0701c8b Compare October 19, 2026 18:12
@KonstantinMirin

Copy link
Copy Markdown
Owner Author

adcontextprotocol#1537 merged.

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.

2 participants