Repository navigation
docs(releasing): write the main, release/N.x, and next branch rules - #1535
Conversation
Record the branch model from adcp-client-python#1525 in RELEASING.md: which branch carries which spec line, how fixes move between them (backports, regular main to next merges, forward-port and backport labels), how next cuts 10.0.0-alpha.N pre-releases from its own release-please setup, the one-time procedure that keeps #1502 on next after main reverts it, and how next merges into main at ratification. CONTRIBUTING.md points feature branches at the right base. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — docs-only PR, no blocking or Medium findings.
This PR touches only CONTRIBUTING.md (+4/-1) and RELEASING.md (+138/-1), documenting the main/release/N.x/next branch model and pointing feature branches at the correct base. The reviewer verified the -s ours revert-recording sequence, PEP 440 normalization (10.0.0-alpha.0→10.0.0a0), pip pre-release semantics, Release-As footer precedence, and the main→next conflict table — all technically accurate. docs(releasing): is the correct conventional-commit prefix. No public surface, wire shape, generated types, or CI gates are touched.
Gate checks: no critical/high findings (row 1 n/a); gated_paths is false (row 2 n/a); high_risk is false (rows 3/5 n/a); no medium findings (rows 4/8 n/a); no prior decision (row 6 n/a); no no-auto-approve team match (row 7 n/a). Falls through to row 9 → approve. Note: review_decision is REVIEW_REQUIRED but that only gates approval when gated_paths is true, which it is not here.
… directory (#1543) * chore(main): release 9.0.0-beta.4 (#1465) * chore(main): release 9.0.0-beta.4 * chore: normalize prerelease version to PEP 440 --------- Co-authored-by: aao-ipr-bot[bot] <279263549+aao-ipr-bot[bot]@users.noreply.github.com> * docs(releasing): write the main, release/N.x, and next branch rules (#1535) Record the branch model from adcp-client-python#1525 in RELEASING.md: which branch carries which spec line, how fixes move between them (backports, regular main to next merges, forward-port and backport labels), how next cuts 10.0.0-alpha.N pre-releases from its own release-please setup, the one-time procedure that keeps #1502 on next after main reverts it, and how next merges into main at ratification. CONTRIBUTING.md points feature branches at the right base. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(governance): require crit markers with their claims, and run the signing tests CI skips (#1539) * test(webhooks): pass operation_id to send_mcp in the webhook sender tests operation_id became a required keyword of WebhookSender.send_mcp in #1066. Twenty-five calls in test_webhook_sender_e2e.py, test_webhook_sender_alt_auth.py, and test_webhook_sender_from_pem.py still omitted it, so 24 tests raised TypeError before they reached the behavior they test. CI doesn't install fastapi, so the modules skip there and nothing reported it (#1524). Each call now passes operation_id. * test(webhooks): give the proxy-env test's fake client the stream method the sender calls Since #1066 the sender posts through client.stream, which bounds the response body it keeps. The fake httpx.AsyncClient in test_owned_client_ignores_https_proxy_env still had only post, so the test failed with AttributeError once the operation_id fix let it run. The fake now yields its response from stream. * test(webhooks): retry with the first delivery's timestamp in the dedupe test An idempotency key binds the payload. test_retry_with_same_idempotency_key_dedupes called send_mcp twice with the same key and no timestamp, so each call stamped its own time, the second body differed, and the receiver answered 409 instead of a duplicate 200. send_mcp's docstring names the conflict. Both calls now pass one timestamp, so the retry repeats the first body and the receiver dedupes it. * test(signing): sign the digest-off FastAPI case with the 3.1 profile Since #1038 the 3.2 signer refuses a non-empty body without content-digest, as AdCP 3.2 request signing requires. The either-False case of test_signed_request_verifies_end_to_end asked the 3.2 signer for exactly that and raised ValueError before it reached the verifier. The case now signs with the 3.1 profile, where content-digest is optional, which is what the either policy accepts; the required case still signs with 3.2. * test(webhooks): re-sign the retry in the HTTP dedupe test test_duplicate_retry_dedupes_over_real_http posted the same body twice with the same Signature and nonce, so the receiver refused the second POST with 401 webhook_signature_replayed before it reached deduplication. A sender's retry repeats the body under a fresh signature, as WebhookSender.resend does. Each POST now carries its own signature, and the second one gets 200 with duplicate=true. * ci: install fastapi and svix with the dev extra Every CI job installs the package with .[dev] or .[dev,pg], and neither extra resolved fastapi or svix. Five signing test modules skip whole on pytest.importorskip("fastapi"), and test_standard_webhooks.py skips its two svix interop tests, so CI never ran them and their failures went unseen (#1524). The dev extra now carries both packages, so each job that runs the signing suite runs these files too. Closes #1524 * fix(governance): require each critical marker, its crit entry, and its claim together security.mdx checklist step 4 (AdCP 3.2.1 to 3.2.5) requires, for authorized_commitment, authorized_task, and authorized_payload_hash, "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". verify_governance_authorization 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, a marker with no crit entry, or a false marker alone. The check now requires the three to be present together, with the marker true. test_critical_marker_crit_entry_and_claim_appear_together re-signs the accepted vector with each header, so only the crit check can refuse it; it failed before this change. * fix(governance): answer a non-string token alg with governance_token_invalid verify_governance_authorization 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_alg_is_an_invalid_token failed before this change. * fix(signing): decode base64url strictly JWS and JWK members are base64url: the RFC 4648 section 5 alphabet with the padding omitted (RFC 7515 section 2). RFC 4648 section 3.3 says implementations MUST reject encoded data that contains characters outside the base alphabet. b64url_decode used the standard library's lenient decoder, which accepted padding and the standard alphabet's + and /, dropped other characters, and ignored trailing bits. Verifier step 8 decodes the signer's JWK through public_key_from_jwk, so the request and webhook verifiers accepted a JWK whose x member had "!!" inserted. b64url_decode now refuses padding, + and /, and decodes the rest with base64.b64decode(altchars=b"-_", validate=True), which refuses any other character. It also refuses non-zero trailing bits (RFC 4648 section 3.5), so each value has the one spelling b64url_encode produces. Every caller already handles the ValueError it raises. test_jwk_members_are_strict_base64url failed before this change. * feat(identity): verify JWS artifacts on the issuer's Web Bot Auth key directory Implements specs/agent-identity-3-3.md R4 as amended by adcontextprotocol/adcp#8299 (WG-approved 2026-10-19). A governance context, designated-task response signing or rights attestation whose issuer origin O is listed by the trust record derived from O is verified with a key from O's key directory: alg EdDSA, kid the JWK thumbprint, no use/key_ops/adcp_use checks, revocation by O's list. Historical governance verification uses the governance key archive at O. A trust record that cannot be read never selects the 3.2 profile; issuers no trust record lists stay on it. adcp.signing.jws_artifacts adds select_jws_artifact_profile, verify_wba_jws_artifact, WbaJwsArtifactVerifier, HttpGovernanceKeyArchive and compact_jws_from_envelope, reusing TrustRecordEvaluator, DiscoveredSignerKeys and OriginRevocationChecker. verify_governance_authorization takes web_bot_auth= and keeps every other check; verify_historical_governance_context verifies retained tokens through the archive. Outages on the new path are governance_jwks_unavailable. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(identity): treat any key lookup failure as an outage on the JWS artifact path A custom key directory or governance key archive that raises anything other than JwsArtifactError now yields jws_key_unavailable (governance_jwks_unavailable) instead of escaping the verifier. An archive entry whose alg is not a string is skipped rather than raising TypeError, and a key the cached archive lacks is refetched at most every 30 seconds, so a key rotated into the archive is not reported unknown for a whole cache period. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: aao-ipr-bot[bot] <279263549+aao-ipr-bot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Constantine Mirin <konstantin.mirin@gmail.com>
What
Writes the branch and port rules from #1525 into
RELEASING.md, and pointsCONTRIBUTING.md's "create a feature branch" step at the right base branch.main(latest ratified AdCP, current major),release/N.x(fixes for older majors),next(draft specs;10.0.0-alpha.Npre-releases only).mainfirst and are backported torelease/N.xby cherry-pick PR.nexttakesmainregularly through true merges, never squash or rebase. Draft-only changes stay onnext.backport release/8.xandforward-port nextlabels mark PRs that need a port.nextmerges intomainat ratification.main→nextmerges, givennext's separate release files.next: release-please-next.yml, its own config and manifest, the alpha channel, PEP 440 normalization, and PyPI pre-release behavior. Plus the one-time setup: addnextto therelease-publishenvironment's deployment branches.next: the-s ourssequence that keeps the identity code onnextafter revert: move experimental AdCP 3.3 identity from main to next #1534 reverts it onmain, with revert-the-revert as the alternative.nextintomainand pick the new major.Why
#1525: draft-spec work must not ship in stable releases, so the repository needs a written rule for which branch carries which spec. adcp-go and adcp-client have the same situation (adcontextprotocol/adcp#8118). The release decision for the Python SDK:
mainstays on AdCP 3.2.x (9.x), and AdCP 3.3 identity work lives onnextuntil 3.3 is ratified.This is a separate PR from the revert (#1534), so the revert commit contains only the revert and can be recorded on
nextwith-s ourswithout dragging docs along. The two PRs are independent.State of
next(already done; it's not a protected branch)next/3.3was renamed tonext(in two hops, because git can't createnextwhilenext/3.3exists). fix(identity): align AdCP 3.3 agent identity with the final spec and the Go and TS SDKs #1514 retargeted automatically, and refactor(signing): RFC 9421 on http-message-signatures; align Web Bot Auth with adcp#7894 #1523 stays stacked on fix(identity): align AdCP 3.3 agent identity with the final spec and the Go and TS SDKs #1514's branch.next(634f527).chore(next): release 10.0.0-alpha.0, withpyproject.tomlnormalized to10.0.0a0.Refs #1525, #1534, adcontextprotocol/adcp#8118.
🤖 Generated with Claude Code