Skip to content

attribution_pagination_payload OR-s the request-level scan-truncated flag into per-kind has_more, producing a non-advancing next_offset (infinite paging loop) #292

Description

@erskingardner

Summary

attribution_pagination_payload computes each attribution kind's has_more by OR-ing the per-kind row count with the request-level truncated flag. Whenever the action scan truncates (a group with more human-action events than GOGGLES_MAX_ACTION_EVENTS_PER_REQUEST), every kind is advertised as has_more=True even when all of its rows have already been returned, and pagination_payload then hands back a next_offset that does not advance — an infinite pagination loop for API consumers.

Code

forensics/views.py:2175-2190:

def attribution_pagination_payload(action_groups: list[dict], filters: dict) -> dict:
    limit = filters["limit"]
    offset = filters["offset"]
    payload = {}
    for kind, page_key in ACTION_ATTRIBUTION_PAGE_KEYS.items():
        total = len([row for row in action_groups if row["attribution_kind"] == kind])
        returned = min(max(total - offset, 0), limit)
        payload[page_key] = pagination_payload(
            limit,
            offset,
            returned,
            total > offset + limit or getattr(action_groups, "truncated", False),  # has_more
        )

forensics/views.py:1686-1693:

def pagination_payload(limit: int, offset: int, returned: int, has_more: bool) -> dict:
    return {
        ...
        "has_more": has_more,
        "next_offset": offset + returned if has_more else None,
    }

truncated is set per request in action_groups_for_api (forensics/views.py:2122) purely from the total event count vs max_events; it is independent of offset, so it is True on every page once the group's action history is large enough.

Concrete failure scenario

  1. A group has more human-action events than GOGGLES_MAX_ACTION_EVENTS_PER_REQUEST, so action_groups_for_api sets truncated=True.
  2. GET /api/v1/groups/<slug>/actions/ (default offset=0, limit=100). Take an attribution kind with, say, 3 fully-returned groups: returned=3, has_more = (3 > 100) or True = True, next_offset = 0 + 3 = 3.
  3. The client follows next_offset=3. Now returned = min(max(3-3,0),100) = 0, but has_more is still True (truncated is still True), so next_offset = 3 + 0 = 3.
  4. The client is pinned at offset=3, has_more=True, zero new rows — forever. For a kind with zero rows the loop pins at offset=0.

Each iteration also re-runs the full bounded action scan (GOGGLES_MAX_ACTION_EVENTS_PER_REQUEST events materialized per request), so an automated consumer following the documented pagination contract spins in place and repeatedly re-scans.

Why not a duplicate

#164 is about api_group_actions materializing all events and paginating in Python (the scan strategy). #147 is group_overview_context setting audit_files_limited via == LIMIT. This is a distinct output-contract bug in the pagination metadata: has_more/next_offset conflate the per-kind "more rows of this kind" signal with the request-level scan_truncated signal, so a fully-returned kind never terminates. scan_truncated is already surfaced as its own top-level key (views.py:2188); it should not also drive per-kind has_more.

Suggested fix

Compute per-kind has_more from that kind's own total only (total > offset + limit), and let scan_truncated remain the separate, already-present signal that the underlying scan was capped. When has_more is derived per-kind, next_offset advances correctly and terminates.

Severity: MEDIUM — breaks the pagination contract for automated API consumers (non-terminating loop) and amplifies work via repeated full scans.

Metadata

Metadata

Assignees

No one assigned

    Labels

    MEDIUMSeverity: important bug or performance issue with bounded impactbugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions