Skip to content

feat(compliance): pagination integrity for get_media_buys#3122

Merged
bokelley merged 3 commits into
mainfrom
claude/issue-3111-get-media-buys-pagination
Apr 25, 2026
Merged

feat(compliance): pagination integrity for get_media_buys#3122
bokelley merged 3 commits into
mainfrom
claude/issue-3111-get-media-buys-pagination

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Closes #3111

Summary

Fourth in the rolling pagination conformance series (#3095, #3100, #3109). Adds cursor-based pagination to handleGetMediaBuys and a new get-media-buys-pagination-integrity.yaml conformance storyboard.

Non-breaking justification: pagination was already an optional field in get-media-buys-response.json ($ref: core/pagination-response.json). Existing callers receive an additional field they can safely ignore; the media_buys array shape is unchanged.

Changes

server/src/training-agent/task-handlers.ts

  • Defensively adds pagination?: { max_results?: number; cursor?: string } to GetMediaBuysArgs (widening in case the generated GetMediaBuysRequest type omits it)
  • When media_buy_ids is present → direct lookup, pagination skipped, all matching buys returned (per request-schema semantics: "When omitted, returns a paginated set")
  • When media_buy_ids is absent → reads max_results (default 50, cap 100), decodes cursor, slices post-filter buys, emits pagination: { has_more, total_count, cursor? }
  • Returns INVALID_REQUEST on malformed cursor
  • New encodeMediaBuyCursor/decodeMediaBuyCursor using mb:offset:<n> prefix — namespaced so a get_media_buys cursor is rejected by decodeCreativeCursor (and vice versa), preventing silent wrong-offset reads. Will unify with shared encodeOffsetCursor once feat(compliance): get_signals pagination cursor integrity #3109 merges

Design note: when both media_buy_ids and pagination.cursor are provided, the cursor is intentionally ignored — ID-lookup semantics win (documented in code comment; flagged and accepted in pre-PR protocol review).

static/compliance/source/universal/get-media-buys-pagination-integrity.yaml

  • 3 active media_buy fixtures via controller_seeding: true
  • Phase 1: capability discovery
  • Phase 2: pagination walk — first_page (max_results=2, assert has_more=true + cursor present) → terminal_page (follow cursor, assert has_more=false + cursor absent)
  • No query_summary assertions — get-media-buys-response.json does not define that field

server/tests/unit/training-agent.test.ts — 3 new tests:

  1. Full pagination walk: create 3 buys → page1 (has_more=true, cursor, total_count=3) → page2 (has_more=false, no cursor, total_count=3)
  2. Malformed cursor → INVALID_REQUEST
  3. media_buy_ids bypasses pagination → all 3 returned, no pagination block

Pre-PR review

  • code-reviewer: approved — no blockers; two nits (server-instance comment in tests, encodeCreativeCursor namespace tech-debt inherited from pre-existing code)
  • ad-tech-protocol-expert: approved — cursor namespace correct; total_count semantics correct (post-filter, pre-slice); storyboard correctly omits query_summary; silent-ignore contract for cursor+ID-lookup conflict is documented in code comment

Nits noted (not fixing in this PR)

  • Test uses three server instances that share a module-level InMemoryStateStore — this is correct behavior, a comment would help future readers
  • GetMediaBuysArgs.pagination uses a local type literal rather than the canonical PaginationRequest type from @adcp/client — low blast radius now, will matter more once feat(compliance): get_signals pagination cursor integrity #3109 ships the shared codec

Session: https://claude.ai/code/session_01YQwRdKHrxUzQrYEGk2dMDT


Generated by Claude Code

claude added 2 commits April 25, 2026 09:17
Add cursor-based pagination to handleGetMediaBuys and a new
get-media-buys-pagination-integrity conformance storyboard.

- Skip pagination when media_buy_ids is present (direct lookup semantics
  per the request schema; callers expect all requested IDs back)
- Broad-scope queries: read max_results (default 50, cap 100), decode a
  namespaced mb:offset:<n> base64url cursor, slice post-filter buys, emit
  pagination: { has_more, total_count, cursor? }
- mb: prefix prevents cross-endpoint cursor reuse with list_creatives
- Return INVALID_REQUEST on malformed cursor
- Storyboard: 3 active media_buy fixtures, capability-discovery + 2-page
  walk (first page has_more=true+cursor, terminal page has_more=false+no cursor)
- Three unit tests: pagination walk, malformed cursor, ID-lookup bypass

Closes #3111

https://claude.ai/code/session_01YQwRdKHrxUzQrYEGk2dMDT
…_ids conflict

When media_buy_ids and pagination.cursor are both present, the cursor is
intentionally ignored — ID-lookup semantics win. Adds explicit comment to
make this design decision undeniable (flagged in pre-PR protocol review).

https://claude.ai/code/session_01YQwRdKHrxUzQrYEGk2dMDT
@bokelley bokelley marked this pull request as ready for review April 25, 2026 13:36
@bokelley bokelley force-pushed the claude/issue-3111-get-media-buys-pagination branch from 843321c to afd99fa Compare April 25, 2026 13:36
@bokelley bokelley merged commit ea93666 into main Apr 25, 2026
19 checks passed
@bokelley bokelley deleted the claude/issue-3111-get-media-buys-pagination branch April 25, 2026 13:40
bokelley added a commit to adcontextprotocol/adcp-client that referenced this pull request Apr 25, 2026
…fallback (#983)

request-builder.get_media_buys and get_media_buy_delivery always
returned { media_buy_ids: [context.media_buy_id ?? 'unknown'] }.
When a storyboard's sample_request omitted media_buy_ids (testing
the broad-list / pagination path), the merge kept the placeholder
because the fixture didn't override it. Agents received a placeholder
ID, returned 0 matches, and pagination assertions failed.

Per get-media-buys-request.json the field is optional (minItems: 1).
Omitting it asks for a paginated set of accessible media buys —
a conformant broad-list query. Mirroring peer paginated-read
enrichers (list_creatives, list_creative_formats, list_accounts,
get_signals) — none of which fabricate IDs — both enrichers now
return empty when context lacks a real media_buy_id. The fixture's
sample_request becomes authoritative on that path.

For get_media_buy_delivery (which requires media_buy_ids per the
spec), the same change turns silent NOT_FOUND responses into
surfacing INVALID_REQUEST errors that authors can debug.

8 new tests in test/lib/request-builder.test.js. Unblocks
adcontextprotocol/adcp#3122 (get_media_buys pagination conformance
storyboard).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Apr 25, 2026
Implements Brian's Option A from #3121 — three coordinated edits to
.agents/routines/triage-prompt.md:

1. **Pre-PR gate now runs `npm run build && npm run typecheck`**
   instead of `npm run precommit`. The full `build` chains
   build:schemas, build:compliance (storyboard idempotency_key,
   contradictions, pagination invariants, doc-parity rows), and
   build:protocol-tarball — every lint CI runs. precommit skips
   the compliance build entirely, which is what bit #3110 (missing
   idempotency_key), #3114 (FIXTURE_CATEGORY_PRIMARY_ID gap),
   #3122 (cursor codec duplication), and three doc-parity gaps.
   The bot's "approved" verdict next to red CI was a trust-eroding
   signal; this fixes it.

2. **PR body now carries a "Triage-managed PR" block** documenting
   that the bot does not iterate on review comments — push fixup
   commits directly or re-trigger via /triage on the source issue.
   Reviewers no longer have to guess whether to wait or push.

3. **`claude-triaged` label is applied to the PR after creation**
   (mirrors the issue label). Searchable from PR list views;
   complements the claude/issue-NNNN-* branch pattern as a
   PR-ownership signal.

Option B from the issue (build a PR review-comment iteration loop)
is deferred to 4.x roadmap per Brian's decision.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.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.

Pagination integrity for get_media_buys

2 participants