Correct overflow trimming and cursor selection for reverse pagination

Consider records ordered by an increasing unique sequence. A backward page query reads in descending order and retrieves one extra record to detect more history, while the response is reversed into chronological order. The key questions are which record is only the lookahead, when it should be removed, and which retained record should define the exclusive cursor for the next request. A concrete eight-record, three-item page trace can expose boundary mistakes.

Reasoning trace establishes the boundary rule. With an exclusive before-sequence cursor, inspect the page-size-plus-one result while it is still descending. The last fetched row is the oldest and is only the lookahead, so remove that row, reverse the retained rows for chronological output, and form the next cursor from the oldest retained row. For sequences one through eight and page size three, the descending fetches are eight, seven, six, five; then five, four, three, two; then two, one. The chronological responses are six, seven, eight; then three, four, five; then one, two. The cursors are six, then three, then absent. Using the removed lookahead as a strict cursor would skip a message.

The security extension is to treat every cursor as untrusted and bind it to the server-derived effective query. The protected cursor state should commit to the authenticated principal or tenant, endpoint purpose, thread binding, canonical effective filters, backward direction, immutable snapshot, ordering version, expiry, and the oldest retained ordering position. Signing alone does not conceal identifiers, so use authenticated encryption or an authenticated random handle to server-side state. Parse and authorize the current request, verify the cursor before trusting any claim, recompute bindings after defaults and authorization filters, compare them in constant time, and reject every tamper or scope mismatch with the same generic restart-pagination error. For mutable filter fields, a high-water sequence is not a full snapshot; use point-in-time revisioned data or a materialized snapshot with a bounded lifetime. Each next cursor copies all bindings and changes only the position.