Skip to content

InMemoryJobInventory pagination cursor is unstable under concurrent inserts #59

@nficano

Description

@nficano

InMemoryJobInventory.list interprets the wire cursor as a non-negative integer offset into the principal's sorted job list at lib/src/main/kotlin/dev/arcp/runtime/JobInventory.kt:47 (cursor?.toIntOrNull()?.coerceAtLeast(0) ?: 0), and the nextCursor it returns at lib/src/main/kotlin/dev/arcp/runtime/JobInventory.kt:56 is just start + page.size. Because the visible list is sorted by createdAt and then jobId at lib/src/main/kotlin/dev/arcp/runtime/JobInventory.kt:54, any new job recorded between two list calls shifts the indices that came after it, so the second page either repeats or skips entries depending on where the new record lands. A malformed cursor — anything not parsable as an int — silently restarts at zero rather than failing with INVALID_ARGUMENT, which both hides bugs and lets a client unknowingly re-walk the same prefix forever.

Fix prompt: Encode the cursor as a stable opaque token tied to the sort key, for example a base64-encoded (createdAt, jobId) tuple of the last entry returned, and resume by skipping past that key. Validate the cursor on input and throw ARCPException.InvalidArgument("cursor") (which the dispatch path will then translate to a correlated Nack) when the value cannot be decoded. Add tests in lib/src/test/kotlin/dev/arcp/runtime/ListJobsHandlerTest.kt that record a job between two list calls and assert the second page neither skips nor duplicates entries, plus a test for a malformed cursor.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingseverity:mediumMedium severity issue

    Type

    No type
    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