id: get_media_buys_pagination_integrity
version: "1.0.0"
title: "Pagination shape — get_media_buys"
category: schema_validation
summary: "Validates that get_media_buys responses carry a well-formed pagination envelope honoring the cursor↔has_more invariant."
track: core
required_tools:
  - get_media_buys

requires:
  - controller

narrative: |
  Every paginated read in AdCP returns a `pagination` envelope satisfying
  the cursor↔has_more invariant: `has_more=true` carries a `cursor`,
  `has_more=false` omits it. The full multi-page round-trip is exercised
  against `list_creatives` (`pagination_integrity.yaml`); this storyboard
  verifies the simpler invariant — that `get_media_buys` emits a valid
  pagination block on every successful response.

  The runner seeds three media buys via `controller_seeding: true`. The
  storyboard's get_media_buys call lands on an ID-lookup path because the
  current `@adcp/client` request-builder injects `media_buy_ids` from
  context (see adcp-client request-builder.js — broad-list queries
  through the SDK are not yet supported for `get_media_buys`). On the
  ID-lookup path the agent MUST still emit `pagination`, with
  `has_more: false` and no cursor, and SHOULD volunteer `total_count`
  reflecting the matched set.

  An agent that omits the `pagination` block entirely on this call —
  triage's pre-#3122 behavior — fails the first-page assertion. The
  multi-page round-trip is parked under adcontextprotocol/adcp-client
  for the request-builder gap; once that lands, this storyboard becomes
  a candidate for upgrade to a seeded continuation+terminal walk like
  `pagination_integrity.yaml`.

agent:
  interaction_model: stateful_preloaded
  capabilities:
    - sells_media
  examples:
    - "Any AdCP media buy seller exposing get_media_buys"

caller:
  role: buyer_agent
  example: "Compliance test harness"

prerequisites:
  description: |
    The runner seeds three media buys via `controller_seeding: true`.
    Each fixture only needs media_buy_id, status, and currency for
    get_media_buys to surface the entry. The full multi-page walk is
    deferred until the SDK supports broad-list queries on
    get_media_buys (see request-builder.js gap).
  test_kit: "test-kits/acme-outdoor.yaml"
  controller_seeding: true

fixtures:
  media_buys:
    - media_buy_id: "pagination_integrity_mb_1"
      status: "active"
      currency: "USD"
    - media_buy_id: "pagination_integrity_mb_2"
      status: "active"
      currency: "USD"
    - media_buy_id: "pagination_integrity_mb_3"
      status: "active"
      currency: "USD"

phases:
  - id: capability_discovery
    title: "Capability discovery"
    narrative: |
      Confirm the agent supports the media buy protocol before
      exercising get_media_buys.
    steps:
      - id: get_capabilities
        title: "Check agent capabilities"
        narrative: |
          Verify the agent declares media_buy in supported_protocols
          before driving get_media_buys.
        task: get_adcp_capabilities
        schema_ref: "protocol/get-adcp-capabilities-request.json"
        response_schema_ref: "protocol/get-adcp-capabilities-response.json"
        doc_ref: "/protocol/get_adcp_capabilities"
        comply_scenario: capability_discovery
        stateful: false
        expected: |
          Return capabilities declaring media_buy in supported_protocols.

        sample_request:
          context:
            correlation_id: "get_media_buys_pagination_integrity--get_capabilities"
        validations:
          - check: response_schema
            description: "Response matches get-adcp-capabilities-response.json schema"
          - check: field_present
            path: "supported_protocols"
            description: "Agent declares supported protocols"

          - check: field_present
            path: "context"
            description: "Response echoes back the context object"
          - check: field_value
            path: "context.correlation_id"
            value: "get_media_buys_pagination_integrity--get_capabilities"
            description: "Context correlation_id returned unchanged"

  - id: pagination_shape
    title: "Verify pagination envelope on get_media_buys"
    narrative: |
      With three media buys seeded, the agent's response MUST carry a
      well-formed `pagination` envelope. On the ID-lookup path
      (current SDK request-builder default), the envelope is terminal:
      `has_more=false`, no cursor. Agents that omit the envelope
      altogether fail this assertion — that's the dishonest shape this
      storyboard catches.

    steps:
      - id: list_call
        title: "Call get_media_buys and verify pagination shape"
        narrative: |
          The buyer requests media buys. Whatever path the SDK
          request-builder selects (today: ID-lookup with the most
          recently seeded media_buy_id), the agent MUST emit a valid
          `pagination` envelope satisfying the cursor↔has_more
          invariant.
        task: get_media_buys
        schema_ref: "media-buy/get-media-buys-request.json"
        response_schema_ref: "media-buy/get-media-buys-response.json"
        doc_ref: "/media-buy/task-reference/get_media_buys"
        comply_scenario: get_media_buys_pagination_integrity
        stateful: true
        expected: |
          Return media_buys with a `pagination` envelope:
          - pagination.has_more is a boolean (false on terminal ID-lookup)
          - pagination.cursor absent or null when has_more is false

        sample_request:
          account:
            brand:
              domain: "acmeoutdoor.example"
            operator: "pinnacle-agency.example"

          context:
            correlation_id: "get_media_buys_pagination_integrity--list_call"
        validations:
          - check: response_schema
            description: "Response matches get-media-buys-response.json schema"
          - check: field_present
            path: "pagination"
            description: "get_media_buys MUST emit a pagination envelope on every response"
          - check: field_present
            path: "pagination.has_more"
            description: "pagination.has_more is REQUIRED per /schemas/core/pagination-response.json"

          - check: field_present
            path: "context"
            description: "Response echoes back the context object"
          - check: field_value
            path: "context.correlation_id"
            value: "get_media_buys_pagination_integrity--list_call"
            description: "Context correlation_id returned unchanged"
