The API reads older messages by applying an exclusive sequence boundary, orders rows newest first, and fetches one extra row to detect more history. Each response page must then be presented oldest first. The key questions are whether trimming happens before or after reversing, which end contains the lookahead row, and how to derive an exclusive next cursor that preserves every item across multiple pages.
Backward cursor pagination with chronological page output
A deterministic trace establishes the invariant. Fetch page size plus one in descending order. When four rows are returned for a page size of three, the fourth and final descending row is the oldest fetched row and is only the lookahead, so remove it before reversing. Reverse the three retained rows for chronological presentation. Construct the next exclusive backward boundary from the oldest retained row, which becomes the first row in the chronological response, never from the discarded lookahead. For sequences one through eight, the pages are six through eight with boundary six, then three through five with boundary three, then one through two with no continuation. Using the discarded rows five or two as boundaries would skip them. This was verified by reasoning through the sequence transitions; no code or automated test was executed.
The design must treat a retained cursor as hostile input and bind it to server-derived request context. On the first page, resolve and authorize the requested conversation, canonicalize effective filters after defaults and authorization restrictions, fix backward direction, and establish a snapshot. Store those bindings with the oldest retained ordering boundary. A signature alone gives integrity but not confidentiality, so use a signed random server-side handle or authenticated encryption; do not expose raw internal identifiers, filters, snapshot handles, or sequence metadata in a readable payload. On continuation, verify authenticity and expiry, independently authorize the currently requested conversation, recompute its conversation and filter bindings, require the direction and snapshot to match, and derive the query only from validated server values. Thread switches, filter changes, direction changes, tampering, expiry, and unavailable snapshots should produce one generic cursor error without echoing identifiers or mismatch details. Preserve the original snapshot and bindings in every next cursor and advance only the exclusive position to the oldest retained row. A high-water snapshot is sufficient only for append-only immutable membership; edits, deletions, or mutable filter fields require temporal reads or a durable result snapshot. This is a conceptual security analysis; no executable tests were run.