Repository navigation
feat(server): seller helper for wholesale feed versions - #1718
Conversation
Backport of #1706 to SDK 9.x (AdCP 3.2), whose get_products / get_signals wholesale fields, if_wholesale_feed_version / if_pricing_version rules and unchanged: true response are the same as 3.3's. adcp.server gains wholesale_feed_version and pricing_version (RFC 8785 canonical, SHA-256, order-independent across items, path-addressed freshness fields ignored, keyed on cache_scope), check_feed_preconditions (the mode and pairing rules, INVALID_REQUEST with field) and feed_unchanged. products_response / signals_response no longer write unchanged: false, and refuse unchanged=True with items or without wholesale_feed_version, both of which the schema already rejects. Differences from #1706: the extra= docs no longer name the 3.3-only collections / signal_pricing_group_rules, and one test compares a model with its own wire form, since SDK 9 dumps unset schema defaults. Refs #1676 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
| "check_feed_preconditions", | ||
| "feed_unchanged", | ||
| "pricing_version", | ||
| "wholesale_feed_version", |
There was a problem hiding this comment.
Medium: Six new public adcp.server exports land here — wholesale_feed_version, pricing_version, check_feed_preconditions, feed_unchanged, FRESHNESS_FIELDS, PRICING_FIELDS — but none appear in AGENTS.md. The repo rule is explicit: a new public export without a matching entry in AGENTS.md's handler/helper reference is a Medium. docs/wholesale-feed-versions.md is thorough, but it is not the agent-consumable surface a coding agent reads. Add a row under DX Helpers (or a short Response Builders note for the unchanged=True path).
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — additive seller-side wholesale feed-version helpers (backport of #1706 to SDK 9.x).
The reviewer found exactly one Medium finding and no critical/high findings. Walking the decision table top-down:
- Row 1 (critical/high): no such findings.
- Row 2 (gated_paths): gated_paths is false — does not fire despite review_decision=REVIEW_REQUIRED.
- Row 3 (deleted high-risk path): high-risk reasons are (modified) and (added) only, no deletions.
- Row 4 (medium + data-loss/schema/infra): the single medium is an AGENTS.md documentation gap (docs/API-surface category), not data-loss/schema/infra — does not fire.
- Row 5 (high_risk modified + medium): init.py and responses.py are (modified) high-risk files AND there is a medium finding — BUT the medium finding is on src/adcp/server/init.py:381, which IS a modified high-risk file. This row fires.
Correction: Row 5 requires high_risk true (yes), a (modified) reason (yes — init.py and responses.py), AND any medium finding (yes). All three conditions are met → escalate.
Medium findings
- src/adcp/server/init.py:381 — New adcp.server exports absent from AGENTS.md
This is a documentation-table gap on a modified high-risk-path file, triggering row 5 (escalate).
Medium findings
- src/adcp/server/init.py:381 — New adcp.server exports absent from AGENTS.md
Backport of #1706 (merged into
next) to SDK 9.x for 9.4.0. #1676 is additive, and the AdCP 3.2get_products/get_signalsschemas have the same wholesale fields,if_wholesale_feed_version/if_pricing_versionrules,unchanged: trueresponse requirements and forecastgenerated_atfields as 3.3.API (
adcp.server)wholesale_feed_version(items, *, cache_scope, ignore=FRESHNESS_FIELDS, extra=None)returns a content token. Each item is RFC 8785 canonical JSON hashed with SHA-256; the token hashes the sorted item digests, so top-level item order doesn't matter. Ignored freshness fields are given as paths.cache_scopeis part of the hash.pricing_version(items, *, cache_scope)is an optional separate token for the pricing layer.check_feed_preconditions(request)enforces the rules: either token needs a wholesale read, andif_pricing_versionneedsif_wholesale_feed_version. A violation raisesAdcpError(INVALID_REQUEST, field=...).feed_unchanged(request, *, feed_version, pricing_version=None)says whether the buyer's tokens are current.products_response/signals_responseno longer writeunchanged: false, and refuseunchanged=Truewith items or without a version. The schema already rejected both outputs.Differences from #1706
extra=docstring and doc no longer name the 3.3-onlycollections/signal_pricing_group_rules.test_model_and_wire_dict_items_give_the_same_tokencompares a model with its own wire form. SDK 9 dumps unset schema defaults (max_bid: false,*_targeting_allowed: false), so a model and its sparse input dict are different wire payloads, and the token rightly follows the wire.Tests
tests/test_feed_version.py(60) passes onmain, alongsidetest_server_dx.pyandtest_feed_mirror.py. black, ruff and mypy are clean on the touched modules.Refs #1676
🤖 Generated with Claude Code