Skip to content

Pagination integrity for get_media_buys #3111

Description

@bokelley

Goal

Fourth in the rolling pagination conformance series (#3095 cursor↔has_more lint + list_creatives storyboard, #3100 total_count honesty, #3109 get_signals — all merged or in flight). This issue extends the same gates onto `get_media_buys`.

Why `get_media_buys` is the next clean target

  • Has `pagination` in both request and response schemas.
  • `comply_test_controller` already exposes `seed_media_buy` (analogous to `seed_creative`), so the storyboard can pre-populate a known fixture set and pin counts deterministically — same shape as `pagination_integrity.yaml`.
  • `handleGetMediaBuys` at `server/src/training-agent/task-handlers.ts:1642` currently emits no `pagination` block. With small fixture sets the bug is dormant but a real seller with hundreds of media buys would silently truncate.

What's needed

1. Training agent fix (`server/src/training-agent/task-handlers.ts:1642`)

Mirror the `handleListCreatives` pattern:

  • Read `req.pagination?.max_results` (default 50, cap 100 per the schema).
  • Decode `req.pagination?.cursor` via the offset-cursor codec (`encodeOffsetCursor('media_buys', n)` / `decodeOffsetCursor('media_buys', cursor)` — added in feat(compliance): get_signals pagination cursor integrity #3109).
  • Slice `buys` by `[offset, offset + max_results]`.
  • Emit `pagination: { has_more, total_count, ...(hasMore && { cursor }) }`.
  • Return `INVALID_REQUEST` when the cursor is malformed.

2. Storyboard (`static/compliance/source/universal/get-media-buys-pagination-integrity.yaml`)

Mirror `pagination-integrity.yaml` (the seeded list_creatives one):

  • `controller_seeding: true` with three `media_buys` entries in `fixtures:`. Pick stable IDs and minimal valid shape (status, currency, dates).
  • Phase 1: `get_adcp_capabilities` (capability discovery boilerplate).
  • Phase 2: pagination walk
    • Step 1 (`first_page`): `get_media_buys` with `pagination.max_results: 2`. Capture `pagination.cursor` into `$context.next_cursor`. Assert `pagination.has_more = true`, `pagination.cursor` present, `pagination.total_count = 3` if volunteered (`field_value_or_absent allowed_values: [3]`), `query_summary` consistency where the schema declares it (check the response schema — get-media-buys-response may not have `query_summary`; if not, drop those assertions).
    • Step 2 (`terminal_page`): `get_media_buys` with `pagination.cursor: $context.next_cursor, max_results: 2`. Assert `pagination.has_more = false`, `pagination.cursor` absent or null, `pagination.total_count = 3` if volunteered.

3. Wiring

  • Add a changeset (empty, descriptive name like `get-media-buys-pagination-integrity.md`).
  • Confirm `build:compliance` picks up the new storyboard (count goes 14 → 15 universal).
  • Run `pagination_integrity` filter against the training agent: should be 3/3 storyboards clean (list_creatives, get_signals, get_media_buys).

Acceptance criteria

  • `npm run build:compliance` clean.
  • `npm run test:pagination-invariant` clean.
  • `pagination_integrity` filter run against the training agent: all storyboards clean.
  • Negative test: flipping `has_more` to false on the first page (or stripping the pagination block) trips the storyboard with the expected diagnostic.
  • Existing `get_media_buys` unit tests still pass (`server/tests/unit/training-agent.test.ts`).
  • Pre-existing `get_media_buys` callers in storyboards (`media-buy-lifecycle`, `sales-*` specialisms) keep working — they don't pass pagination today and shouldn't be affected by the addition.

Pointers

  • Pattern: `pagination-integrity.yaml` and `get-signals-pagination-integrity.yaml`
  • Cursor codec: `encodeOffsetCursor` / `decodeOffsetCursor` at `server/src/training-agent/task-handlers.ts:2087-2127`
  • Handler to modify: `handleGetMediaBuys` at `server/src/training-agent/task-handlers.ts:1642`
  • Schema: `static/schemas/source/media-buy/get-media-buys-response.json`
  • Storyboard authoring: `docs/contributing/storyboard-authoring.md`

Out of scope

  • Changing the query_summary structure of the response (just add the pagination envelope).
  • Cross-storyboard regressions in the sales-* specialisms (they shouldn't touch this path).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingclaude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions