Skip to content

docs(releasing): write the main, release/N.x, and next branch rules - #1535

Merged
bokelley merged 1 commit into
mainfrom
bokelley/1525-branch-rules
Oct 18, 2026
Merged

bokelley merged 1 commit into
mainfrom
bokelley/1525-branch-rules

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

What

Writes the branch and port rules from #1525 into RELEASING.md, and points CONTRIBUTING.md's "create a feature branch" step at the right base branch.

  • Branches: main (latest ratified AdCP, current major), release/N.x (fixes for older majors), next (draft specs; 10.0.0-alpha.N pre-releases only).
  • Ports: fixes land on main first and are backported to release/N.x by cherry-pick PR. next takes main regularly through true merges, never squash or rebase. Draft-only changes stay on next. backport release/8.x and forward-port next labels mark PRs that need a port. next merges into main at ratification.
  • Conflict rules for main → next merges, given next's separate release files.
  • Releasing from 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: add next to the release-publish environment's deployment branches.
  • Moving feat(identity): add experimental AdCP 3.3 agent identity #1502 to next: the -s ours sequence that keeps the identity code on next after revert: move experimental AdCP 3.3 identity from main to next #1534 reverts it on main, with revert-the-revert as the alternative.
  • Ratification: the steps to merge next into main and 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: main stays on AdCP 3.2.x (9.x), and AdCP 3.3 identity work lives on next until 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 next with -s ours without dragging docs along. The two PRs are independent.

State of next (already done; it's not a protected branch)

Refs #1525, #1534, adcontextprotocol/adcp#8118.

🤖 Generated with Claude Code

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>

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

@bokelley
bokelley merged commit 5df0036 into main Oct 18, 2026
47 checks passed
@bokelley
bokelley deleted the bokelley/1525-branch-rules branch October 18, 2026 19:43
bokelley added a commit that referenced this pull request Oct 20, 2026
… 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>
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