Skip to content

feat(client)!: align safety and asset APIs with JavaScript v13 - #1032

Merged
bokelley merged 8 commits into
mainfrom
feat/js-v13-parity-cycle
Aug 17, 2026
Merged

feat(client)!: align safety and asset APIs with JavaScript v13#1032
bokelley merged 8 commits into
mainfrom
feat/js-v13-parity-cycle

Conversation

@bokelley

@bokelley bokelley commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • make the legacy ADCPClient.handle_webhook() HMAC path fail closed, process and route from authenticated body fields, redact webhook activity metadata, and require a literal/scoped compatibility opt-in
  • expose a real 20-member AssetInstance discriminated union, AssetInstanceType, and every variant through stable root, adcp.types, and adcp.types.creative names (preserving Python's established *Content names where they differ from JavaScript)
  • add lookup_brand(..., fresh=True) plus bounded registry HTTP error metadata and clamped header/body retry guidance (retryAfterMs, retryAfter, and retry_after)
  • update AdCP 3.1.15 documentation, package migration guides in source distributions, and separate RFC 9421 WebhookReceiver guidance from the deprecated HMAC helper

Breaking change

ADCPClient.handle_webhook() no longer accepts unsigned MCP callbacks by default. This PR is deliberately marked breaking so release automation cannot publish the change in a 7.x patch.

Release automation is configured to open the SDK 8 prerelease line at 8.0.0-beta.1 (8.0.0b1 in Python package metadata). The bundled protocol remains AdCP 3.1.15 stable; AdCP 3.2 beta will be picked up separately.

Public/conformant receivers should use WebhookReceiver for RFC 9421 verification and delivery deduplication. A 3.x registration that explicitly selects legacy HMAC-SHA256 can continue using ADCPClient with webhook_secret. Receivers isolated from untrusted networks can temporarily opt in with allow_unauthenticated_webhooks=True; a multi-agent client must scope that exception by agent ID.

See MIGRATION_v7_to_v8.md.

Compatibility notes

  • A2A callback handling is unchanged.
  • Existing RegistryError.status_code behavior remains intact.
  • Python's previously established ImageContent, VideoContent, etc. names remain canonical; the conflicting pre-4.0 *Asset aliases are not reintroduced.

Expert review

Code, protocol, and security experts completed two review passes. Their findings are resolved: strict boolean/scoped webhook escape handling, authenticated-body processing and routing, telemetry redaction, overflow-safe registry retry hints, complete union semantics, stable Python asset naming, discoverable migration guidance, and correct breaking-release classification.

Validation

  • 6404 passed, 41 skipped, 9 deselected, 1 xfailed
  • mypy: 948 source/type-contract files clean
  • targeted pre-commit suite: Black, Ruff, Bandit, public API snapshot, and adopter type-check fixtures
  • release configuration and prerelease-normalization tests: 6 passed
  • source distribution and wheel build; both migration guides verified in the source distribution
  • the three JavaScript-v13 storyboard failures also occur unchanged on main at the 7.0.2 release commit; all other CI jobs passed before the review commit

Part of #1031.

@bokelley
bokelley marked this pull request as ready for review August 17, 2026 05:12
Comment thread src/adcp/client.py Outdated
f"Webhook signature verification failed for agent {self.agent_config.id}"
)
raise ADCPWebhookSignatureError("Invalid webhook signature")
elif not self.allow_unauthenticated_webhooks:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

MUST FIX: This flips the MCP webhook default from fail-open to fail-closed. An adopter on the released 7.0.2 line with an MCP webhook receiver and no webhook_secret handled unsigned callbacks fine; after this change the same code raises ADCPWebhookSignatureError. That is a breaking behavior change, and it ships under a fix: commit — release-please cuts a patch (7.0.3), so the break lands on every auto-upgrade without a major.

Two problems compound here: (1) per repo policy a breaking diff under a non-breaking conventional-commit prefix is a high finding, and (2) the WG constitution treats webhook signature/auth posture as never patch-eligible. Carry fix!: / feat!: or a BREAKING CHANGE: footer so this cuts a major. The additive asset exports and RegistryError fields in this same commit are feat:-class (minor) and are also under-signalled by fix:.

@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: Request changes

Request changes — 1 blocking finding.

Blocking findings

  • src/adcp/client.py:5090 — Breaking webhook default-flip ships under fix: prefix (patch). Per the repo's mandatory semver-signal rule, a breaking change to public/wire behavior (flipping the MCP webhook default to fail-closed) plus additive public exports must carry feat!: / fix!: or a BREAKING CHANGE: footer. release-please cuts a patch/minor from a fix: prefix, so the break would ship without a major bump.

Otherwise the diff is clean and defensive: bounded Retry-After and error bodies, preserved timeouts via _request, the A2A path genuinely untouched, and additive registry-backed exports with structured/bounded RegistryError recovery metadata. No forward-compat, import-layering, generated-code, or credential issues found. Re-label the commit with the appropriate breaking prefix/footer (migration note is present in MIGRATION_v6_to_v7.md) to clear this.

Decision table: Row 1 fires — a high severity finding is present.

Blocking findings

  • src/adcp/client.py:5090 — Breaking webhook default-flip ships under fix: prefix (patch); requires feat!:/fix!: or BREAKING CHANGE: footer per mandatory semver-signal rule

BREAKING CHANGE: ADCPClient.handle_webhook now rejects unsigned MCP callbacks unless the isolated-receiver compatibility escape is explicitly enabled.
@bokelley bokelley changed the title fix(client): close JavaScript v13 parity gaps feat(client)!: align safety and asset APIs with JavaScript v13 Aug 17, 2026
aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Aug 17, 2026

@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.

The prior high finding — a breaking webhook fail-closed default-flip shipping under a non-breaking fix: prefix — has been resolved. The PR now carries feat(client)!, which forces a major version bump across the release, correctly signaling the break per the repo's semver-on-public-surface gate.

This is a subsequent pass. The remaining delta is hardening plus additive public surface: authenticated raw-body processing on the legacy HMAC path, activity-metadata redaction, a strict/scoped allow_unauthenticated_webhooks opt-in, a real 20-member AssetInstance discriminated union mirroring the generated AssetVariant, and bounded registry retry-hint parsing. The reviewer confirms no forward-compat regression on discriminated unions and no import-layering violation. All changes are well-tested.

No critical, high, or medium findings in this run.

Decision-table walk:

  • Rows 1: no critical/high findings — skip.
  • Row 2: gated_paths is false — skip (the CHANGES_REQUESTED review_decision does not gate here since no path gate applies).
  • Row 3: no (deleted) high-risk reasons (all (modified)) — skip.
  • Rows 4–5: no medium findings — skip.
  • Row 6: prior decision was request-changes (not escalate) — sticky rule does not apply.
  • Row 7: no no-auto-approve team match — skip.
  • Row 8: fewer than three medium findings — skip.
  • Row 9: approve.

Net improvement over the prior run: the blocking break is now correctly labeled and no new blocking or medium issues surfaced.

aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Aug 17, 2026

@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 — clean pass with no blocking findings.

The reviewer surfaced no critical, high, or medium findings. The delta since the prior approve tightens MCP webhook routing to derive task_type/operation_id from the authenticated payload body rather than untrusted URL arguments, moves legacy-vs-canonical surface selection onto the authenticated body, reduces WEBHOOK_RECEIVED activity metadata to task_id/status/protocol, and rewrites _bounded_retry_seconds to clamp oversized retry hints and avoid OverflowError on unrepresentable int bodies. Both logic changes are fail-closed and pinned by new tests.

High-risk flag is true, but all four matching files (src/adcp/__init__.py, src/adcp/types/__init__.py, src/adcp/types/_eager.py, src/adcp/types/aliases.py) are (modified) with no medium-or-higher findings, so row 5 does not fire and the modifications are presumed safe. No gated paths, no no-auto-approve team match, no deletions.

Decision table: rows 1–8 do not fire (no critical/high, gated_paths false, no deletions, no medium findings, prior decision was approve, no team gate, fewer than three mediums) → row 9 approve.

Release-As: 8.0.0-beta.1
aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Aug 17, 2026

@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 — release-automation and docs delta only, no blocking findings.

What I checked:

  • No no-auto-approve team match; gated_paths: false — no hard gates apply.
  • high_risk: true, but all four high-risk reasons are (modified) type-system files with NO findings on them. Row 5 requires a medium finding to escalate — none exist. No (deleted) reasons (row 3 N/A).
  • Delta since the prior approve is purely release-please config (versioning=prerelease, prerelease-type=beta, prerelease=true to enter the SDK 8 beta line), README version-support prose/example updates to 8.0.0b1 against AdCP 3.1.15, and the renamed test_release_configuration.py asserting the three new config keys.
  • Chain is internally consistent: SemVer-to-PEP440 normalizer maps 8.0.0-beta1 → 8.0.0b1; manifest/pyproject stay at 7.0.2 so the worktree-version test still holds.
  • No public-API, wire-shape, discriminated-union, import-layering, or code-path changes in this delta. No credential/ctx_metadata concerns. CI gates remain armed.

No critical/high/medium findings. Rows 1–8 do not fire → row 9 approve.

aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Aug 17, 2026

@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 — no blocking or medium findings.

Changes are confined to the v3 reference-seller example (list_creative_formats_legacy gains a video_30s catalog entry and filters the static catalog to req.format_ids with trailing-slash-normalized (agent_url, id) keys; empty/None format_ids correctly falls through to the full catalog) and storyboard CI plumbing (the shell runner gates --test-kit behind a capability probe plus a kit-file existence check, fail-closed). A new test covers the trailing-slash filter path.

No public adcp.* surface, wire-shape, forward-compat, generated-code, or credential paths are touched. The high_risk flag fired only on (modified) entries under src/adcp/types/** and __init__.py, but the reviewer surfaced no medium-or-higher concerns on those files, so the modifications are presumed contract-preserving. No no-auto-approve team match, gated_paths is false, and prior decision was also approve.

Decision table: rows 1–8 do not fire (no critical/high/medium findings, no deleted high-risk paths, no gate, no team match, fewer than three mediums) → row 9 approve.

aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Aug 17, 2026

@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 — clean CI fix on the reference-seller example with no blocking or medium findings.

What I checked:

  • Delta since the prior approve is a single focused fix: list_creative_formats_legacy now builds its static catalog via LegacyFormat.model_construct(format_id=LegacyFormatId(...)) instead of model_validate on a dict. This preserves the byte-exact origin-only agent_url that AnyUrl coercion would otherwise slash-normalize. The reviewer confirms model_construct is correct here — LegacyFormatId still runs its own field validators and the remaining Format fields are optional with None defaults.
  • Added a regression test asserting the wire output, so the byte-exact behavior is now guarded.
  • high_risk is true (modified src/adcp/__init__.py, types/__init__.py, _eager.py, aliases.py), but the reviewer surfaced no medium-or-higher concerns on these modified files, so the flag is heuristic only. No (deleted) reasons.
  • gated_paths: false; no no-auto-approve team match.

Decision path: No critical/high/medium findings (row 1, 4, 5 do not fire). Row 2 does not apply (gated_paths false). Row 3 no deletions. Prior decision was approve, so row 6 is moot. No team gate (row 7). Fewer than three medium findings (row 8). Falls through to row 9 → approve.

@bokelley
bokelley enabled auto-merge (squash) August 17, 2026 21:33
Comment thread .github/workflows/ci.yml
skip_counts = summary.get('skipped_by_reason') or {}
allowed_partial_skips = {
'missing_test_controller',
'missing_tool',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium: missing_tool is accepted unconditionally, but nothing in the gate ties it to the absent controller. The comment reads it as "missing controller tools," yet SDK 13 emits missing_tool for any tool the storyboard needs and can't find on the upstream — including a real AdCP task tool the translator should support. Because the accept only requires controller_detected is False, steps_failed == 0, and missing_test_controller > 0, an upstream that silently drops a genuine capability tool produces a missing_tool skip, keeps steps_passed > 0 from the rest, and grades partial → CI goes green on a real coverage regression this conformance gate exists to catch. missing_test_controller and prerequisite_failed are genuinely controller-cascade; missing_tool is not scoped to it. Consider dropping missing_tool from the allowed set, or asserting its skips are controller-tool-scoped.

@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 — clean subsequent review, no findings.

This is a follow-up to a prior approve. The delta since then is a single, tightly-scoped change to the Assert storyboard passed CI gate in .github/workflows/ci.yml: it now accepts overall_status == 'partial' only under a fail-closed topology (zero failed steps/tracks, positive executed coverage, explicit controller absence, no skip class outside the allowlist). The loosening is justified by SDK 13 grading a controllerless translator as partial, and the residual coverage-collapse window remains covered by the separate anti-façade upstream-traffic gate. This is not disarming a CI gate — it stays armed and fail-closed on missing/malformed keys.

The high-risk flag is true only due to (modified) type-system files (src/adcp/__init__.py, types/__init__.py, _eager.py, aliases.py), but the reviewer surfaced no medium-or-higher concerns on any of them, so the modifications are presumed safe. No no-auto-approve team match, gated_paths is false, no deletions.

Decision-table walk: no critical/high (rows 1 skip), gated_paths false (row 2 skip), no deletions (row 3 skip), no medium findings (rows 4/5/8 skip), prior decision was approve not escalate (row 6 skip), no team gate (row 7 skip). Falls through to row 9 → approve.

@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 — CI gate scope refinement with one non-blocking Medium.

What changed since prior approve: A single ci.yml delta. The storyboard hard gate now accepts a tightly-scoped partial topology for the controllerless translator instead of requiring overall_status == passing. The reviewer confirms the accept remains fail-closed on real failures (steps_failed/tracks_failed==0, controller_detected False, no unexpected skip class, steps_passed>0) with a documented rationale, so the gate is not disarmed.

Medium findings (non-blocking):

  • .github/workflows/ci.yml:706 — missing_tool skip accepted unconditionally in the storyboard partial gate rather than scoped to the absent controller, leaving a plausible path for a real upstream capability gap to grade partial and pass.

Decision path: No critical/high findings (row 1 no). gated_paths false (row 2 no). No (deleted) high-risk reasons (row 3 no). The single Medium is a CI/infra-adjacent gate-scope concern, but it is a warn-only/scope-gap category, not a data-loss/schema/infra migration finding that row 4 targets — no destructive or schema/infra change is involved (row 4 no). High-risk files are (modified) but there is a Medium — however row 5 requires the medium to attach to a modified high-risk file; this Medium is on ci.yml, which is not a high-risk-matched path, so row 5 does not fire. Prior decision was approve, not escalate (row 6 no). No no-auto-approve team match (row 7 no). Only one Medium, well under three (row 8 no). Falls through to row 9 → approve.

The high-risk flag reflects (modified) type-system files, but the reviewer surfaced no medium-or-higher concern on any of them, so those modifications are presumed safe.

Medium findings

  • .github/workflows/ci.yml:706 — missing_tool skip accepted unconditionally in storyboard partial gate

@bokelley
bokelley merged commit 0b8bebc into main Aug 17, 2026
27 checks passed
@bokelley
bokelley deleted the feat/js-v13-parity-cycle branch August 17, 2026 21:40
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