feat(decisioning): boot-time validator for declared idempotency vs wired @wrap#543
Merged
Conversation
…red @Wrap Catches the silent-lie configuration: a platform that advertises capabilities.adcp.idempotency.supported=True on the AdCP get_adcp_capabilities response while never applying @IdempotencyStore.wrap to any handler method. Buyers reading the capabilities envelope assume retries are deduped; without the decorator every retry re-executes side effects. * IdempotencyStore.wrap registers the wrapper in a private WeakSet — defense-in-depth over a public sentinel attr (which adopters could spoof and silently defeat the validator). * validate_idempotency_wiring fires from create_adcp_server_from_platform next to the existing property_list / webhook_sender / capabilities boot gates. AdcpError(recovery="terminal") on misconfig. * Loose check by design: "any method wrapped" passes. Tightening to per-method coverage requires the spec to expose a canonical mutating-tool enum, which AdCP #2315 doesn't yet. * Escape hatch: _adcp_idempotency_external = True on the platform class for adopters with upstream gateway dedup or BYO middleware the SDK can't introspect. * Error details carry candidate_methods, decorator_import path, and both opt-out flags so coding agents can auto-resolve the misconfig. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The new boot-time validator (validate_idempotency_wiring) caught the multi-platform-seller example advertising IdempotencySupported(supported=True) on both child platforms and the PlatformRouter union without any @IdempotencyStore.wrap applied. That's the silent-lie configuration the validator exists to catch — switch the mocks to IdempotencyUnsupported to honestly reflect what they implement. Real adopters with in-memory dedup keep supported=True and wrap their mutating handlers; this example doesn't model that surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the silent-lie configuration where a platform advertises
capabilities.adcp.idempotency.supported=Trueon the AdCPget_adcp_capabilitiesresponse but never applies@IdempotencyStore.wrapto any handler method. Buyers reading the capabilities envelope assume retries are deduped; without the decorator, every retry re-executes side effects.IdempotencyStore.wrapregisters the wrapper in a privateWeakSet(defense-in-depth over a spoofable sentinel attr)validate_idempotency_wiringfires fromcreate_adcp_server_from_platformnext to the existingproperty_list/webhook_sender/ capabilities boot gates —AdcpError(recovery="terminal")on misconfig_adcp_idempotency_external = Trueon the platform class for adopters with upstream gateway dedup or BYO middleware the SDK can't introspectdetailscarrycandidate_methods,decorator_importpath, and both opt-out flags so coding agents can auto-resolve the misconfigTest plan
tests/test_validate_idempotency_wiring.pycover:is_wrappedsemantics (incl. attr-spoofing rejection, bound-method resolution), capability reader (object-shaped capabilities, missing arms), validator units (no cap / unsupported / wrap-OK / async-but-unwrapped raises / external opt-out / property-with-boot-side-effect doesn't blow up the scan / dynamic bind in__init__), end-to-end throughbuild_asgi_app(lying platform raises, honest platform boots, gateway-dedup platform boots)tests/test_idempotency*.py,tests/test_decisioning_property_list.py,tests/test_decisioning_capabilities_submodule.py,tests/test_testing_decisioning.py(205 tests)ruff check+mypyclean on changed filesReviewed by
Pre-PR review by
code-reviewer,ad-tech-protocol-expert,dx-expertagents — all material concerns addressed in the final commit (WeakSet over attr sentinel,dir()+try/except overinspect.getmembers, escape hatch, error details for agents, real-async test coverage instead of lambdas).🤖 Generated with Claude Code