Repository navigation
feat(capabilities): self-report adcp.implementation.sdks (#1646) - #1812
Conversation
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — additive AdCP 3.3 implementation self-report (adcp.implementation.sdks[]), shipped under feat(capabilities):.
Checked:
- Semver signal correct — all new surface (
ImplementationReport,adcp_sdk_entry,report_implementation,Implementation/Sdk/ImplementationComponent) is purely additive; no public export removed/renamed, no required→optional flips, no enum members dropped. Non-breaking prefix is accurate. - Fail-closed component derivation — capabilities claimed only when provably agent-wide (idempotency gated on every mutating task being
IdempotencyStore.wrapped +has_mutatingat implementation.py:502; schema_validation withheld under 2.5 legacy adapter at :462; auth withheld on widened discovery sets at :598-609). - DR-0032/0033 pathway — handled as droppable, gated to 3.3+ on both application and wire version (:586-591), with a 3.0–3.2 regression test. Pathway + test present.
- Import layering respected — domain-path imports only, no
_generated.pyreach-in;_COMPONENT_ORDERpinned to the generated enum by test. - No generated-code hand-edits, no credentials in
ctx_metadata, CI gates intact.
Non-blocking note: _schema_shape swallows bundle-load errors (:369) — could theoretically over-claim webhooks on a missing bundle, but schemas ship bundled so this is practically unreachable; it only ever weakens an advisory, non-trust-boundary claim.
high_risk is true but all reasons are (modified) or (added) files with zero medium-or-higher findings — no escalation trigger. No findings, no gated paths, no team gate. Falls through to row 9.
5406418 to
ec1f205
Compare
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — AdCP 3.3 adcp.implementation.sdks[] self-report, additive and version-gated.
No blocking or medium findings. The reviewer confirmed the change is additive, opt-out, and fail-closed throughout:
- Correct non-breaking semver signal (
feat:, notfeat!:), with a MIGRATION_v9_to_v10.md row for the new 3.3 response key. _stampgates on both application and wire version; 3.2 callers never see the block (covered by test_pre_3_3_callers_never_see_the_block).- 3.3→3.2 pathway is droppable on a non-mutating discovery response — compliant with the minor-release-pathway rule, and tested.
_factsonly claims idempotency whenmutating_covered and has_mutating;_schema_shapebare except only weakens claims;__call__broad except is warn-only on advisory (non-durable/non-billing) state — acceptable.- Import layering clean (domain-path only, no
_generatedreach-in); forward-compat unions andctx_metadatacredentials untouched. - Tests cover every derivation branch and round-trip through create_tool_caller with strict validation.
Decision table: no critical/high (rows 1,6 n/a); gated_paths false and review_decision APPROVED (rows 2 n/a); high_risk is true but reasons are (added)/(modified) with zero medium findings (rows 3,4,5 n/a); no no-auto-approve team (row 7 n/a); fewer than three medium findings (row 8 n/a). Falls through to row 9 → approve. Prior decision was also approve; this is a clean follow-up.
Checked: semver signal, forward-compat unions, credential handling, version-translation pathway, public-export docs, test coverage.
AdCP 3.3 lets an agent report the SDKs it runs and the server-side
components each supplies. serve() (and adcp.decisioning.serve) now stamps
{"name": "adcp", "version": <installed>, "components": [...]} on
get_adcp_capabilities responses served at 3.3 or later, via an SDK
response enhancer that runs ahead of the adopter's own.
Components are claimed only when provably agent-wide: transport always;
types for Decisioning platforms; schema_validation for strict request and
response validation without the 2.5 legacy adapter; authentication for
BearerTokenAuth with no widened discovery set; idempotency when every
advertised mutating task is IdempotencyStore-wrapped; webhooks / signing
only when the SDK owns and signs every outbound webhook; lifecycle never.
A declared adcp.implementation always wins (DecisioningCapabilities.adcp
.implementation is now projected; capabilities_response gains
implementation=). serve(report_implementation=False) opts out;
ImplementationReport adds components or further sdks[] entries. 3.0-3.2
callers and 3.2-pinned servers never see the block.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ec1f205 to
382302a
Compare
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean pass, no blocking or medium findings.
This is a subsequent review (prior decision: approve). The reviewer confirmed a clean pass over the one new load-bearing file, src/adcp/server/implementation.py, and the supporting changes.
What was checked:
- Conservative capability derivation:
_static_componentsgates schema_validation on strict request+response and excludes 2.5 legacy adapters;_sdk_owns_task_webhooksclaimswebhooksonly when the SDK outbox owns delivery (verified against webhook_emit.py and PlatformHandler attrs);_stampdouble-gates on application + wire version ≥ 3.3-beta.0 and never overwrites a declaredadcp.implementation. - Semver signal correct: new exports and params are additive under
feat(capabilities):, no!and no breaking change to public surface. - DR-0032/0033 pathway: Droppable on a response field (3.0–3.2 callers never see it) with a test — pathway handling present.
- Import layering clean:
implementation.pyimports only domain paths, no_generatedor numbered classes; no hand-edits todomains/or_generated.py. - No credentials in
ctx_metadata; CI gates untouched.
Decision-table walk: no critical/high findings (row 1 n/a); gated_paths=false (row 2 n/a); high_risk reasons are only (modified)/(added), no (deleted) (row 3 n/a); no medium findings (rows 4, 5, 8 n/a); prior decision was approve, not escalate (row 6 n/a); no no-auto-approve team match (row 7 n/a). Falls through to row 9 → approve.
Note on high_risk: flag is true but driven by (modified)/(added) files with zero medium-or-higher findings — presumed safe per the reading-the-flag guidance. Not escalation-worthy.
Spec
AdCP 3.3 adds
get_adcp_capabilitiesadcp.implementation.sdks[]: packagename, exact resolvedversion, and the server-sidecomponentsthe SDK actually supplies. Thecomponentsdescription is normative. List a component only if every responsibility that applies to it holds across all advertised task paths. Leave it out if coverage varies between paths. An empty list reports SDK use without claiming any component. A missing block means unknown.Design
Automatic self-report.
adcp.server.serve()(andadcp.decisioning.serve(), which delegates to it) installsadcp.server.implementation.ImplementationReporter, an SDK response enhancer. It runs on the sharedcreate_tool_callerpath for MCP and A2A, before the adopter's ownresponse_enhancer, so that enhancer can still amend or remove the block. On a successfulget_adcp_capabilitiesresponse it adds:{"name": "adcp", "version": "<importlib.metadata.version('adcp')>", "components": [...]}The version is the exact installed distribution version. If it can't be resolved, or it breaks the schema pattern (
\S, at most 32 chars), nothing is emitted.Component derivation (conservative):
transportserve()typesPlatformHandler(a plainADCPHandlermay return raw dicts)schema_validationstrict, and the served versions exclude the2.5legacy adapter. Its translated responses are not re-validated, so the default version set never claims this.authenticationauth=BearerTokenAuth(...)on an HTTP transport, with noallow_unauthenticatedand nomcp_discovery_tools/a2a_discovery_skillsbeyondDISCOVERY_TOOLSidempotencyadcp.idempotency.supported: true,_adcp_idempotency_externalis not set, and every advertised task whose bundled request schema requiresidempotency_keyresolves to anIdempotencyStore.wrapped method (platform method for Decisioning). Tasks backed byAccountStore, such assync_accounts, therefore block the claim.webhookstask_webhook_owner_readyand not external),webhook_signing.supported, and no advertised task takes a webhook the SDK doesn't deliver (reporting_webhook,artifact_webhook,revocation_webhook,webhook_url,sync_agent_notification_configs). So a media-buy seller never claims it, because reporting webhooks are delivered by adopter code.signingrequest_signature_verificationis wired,request_signing.supportedis declared, and outbound webhooks are either SDK-signed (webhooksclaimed) or not offered at alllifecycleassert_*_transition, not by the frameworkOverride, opt-out and extension:
adcp.implementationis never overwritten. That coversAdcp(implementation=...)onDecisioningCapabilities(previously dropped by the projection, now emitted), the newcapabilities_response(implementation=...), and a custom handler's own response.serve(report_implementation=False)/ServeConfig(report_implementation=False)omits the block.serve(report_implementation=ImplementationReport(extra_components=["lifecycle"], extra_sdks=[...]))adds components the SDK can't detect, or further SDK entries. An extra entry namedadcpis refused.adcp.server.adcp_sdk_entry(components)builds this SDK's entry for a hand-written block.Implementation,SdkandImplementationComponentare exported fromadcp.decisioning.capabilities(bundled models, so they fitAdcp).Version projection. The block is added only when both the application version and the caller's wire version are at least
3.3-beta.0. A 3.0–3.2 caller, or a 3.2-pinned server, never sees it, which also avoids a recorded conversion loss on every 3.2 discovery call. Before 3.3,adcp.implementationwas an open extension point. As a backstop, the existing 3.3 → 3.2 conversion already drops/adcp/implementation(field_unavailable).Outside
serve()(a directhandler.get_adcp_capabilities(),create_mcp_server,adcp.testing), nothing is emitted, since transport ownership can't be shown there; the spec reads that as unknown.Breaking? It's additive: an optional field the 3.3 schema defines. Tests that compare a whole 3.3 capabilities response served through
serve()will see the new key.MIGRATION_v9_to_v10.mdsays so and documents opt-out and override, so this is not marked!.Tests
tests/test_implementation_report.pydrives the reporter throughcreate_tool_callerwith strict validation, so negotiation, conversion and response-schema validation all run on the stamped response:GetAdcpCapabilitiesResponseschema_validationis withheld for warn-only, partial or off validation and when 2.5 is servedauthenticationderivation: claimed for full coverage; withheld forallow_unauthenticated, a widened discovery set, or stdiosigningneeds the verifier, the declaration, and no unsigned webhookstypesandidempotencyclaimed when every mutating task is wrapped; withheld for unwrapped or external; a declared block wins; no SDK outbox means nowebhooks/signing; a direct handler call emits nothingextra_components/extra_sdksappended and validatedserve()installs the reporter by default;report_implementation=FalseandServeConfigopt-out and custom reportDocs:
docs/adcp-3.3-sdk-inventory.mdrows foradcp.implementation.sdks[]andadcp.implementation, plus aMIGRATION_v9_to_v10.mdrow.Closes #1646
🤖 Generated with Claude Code