Repository navigation
feat(dgw): page recording search with a cursor - #2137
irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 1 commit into
Conversation
|
Lines changed, against #2112:
|
Intent drafts: handoffThe ProvenanceLevels: 1 = my words verbatim, 2 = my words tidied, 3 = agent wording I adopted, 4 = agent fill-in, not intent.
Agent fill-in (level 4, not in the intent file): the cursor is hex-encoded JSON, not signed. A changed cursor can only move the read position inside the logs the same search reads, since its recording ID and file name are matched against the listing and never joined into a path. Left out on purpose
Placement
Pending experimentsNone. |
5224627 to
100d71d
Compare
0122f4c to
c3292a5
Compare
c3292a5 to
4ea8c83
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Numeric page sizes exceeding u32 return 422 instead of the promised 400.
1 open issue
What changed in this PR
Adds stateless cursor pagination to recording search so clients can retrieve results across bounded pages.
Changes:
- Replaces
limitReachedwithnextCursorand addsmaxHits. - Documents paging behavior and cursor constraints.
- Adds pagination and cursor-validation tests.
| File | Description |
|---|---|
| testsuite/tests/cli/dgw/recording_search.rs | Adds HTTP pagination and cursor tests. |
| testsuite/Cargo.toml | Adds hex support for cursor tests. |
| devolutions-gateway/src/api/jrec/search.rs | Implements cursor pagination and unit tests. |
| devolutions-gateway/src/api/jrec/search.intent.md | Defines pagination semantics. |
| Cargo.lock | Records the test dependency. |
Review effort: Balanced
f05df42 to
170f536
Compare
7606fd7 to
29adde5
Compare
Searching every recording doesn't fit in one response, so `POST /jet/jrec/search` now returns pages. A page ends after reading about 16 MiB of logs or after `maxHits` hits, from 1 to 1000 and 1000 by default; 0 or more than 1000 is a 400. `nextCursor` replaces `limitReached`: sending the same `query`, `recordings` and `startTime` with it as `cursor` gets the next page, until a page has no `nextCursor`. Gateway keeps no state between pages. The cursor holds the recording, file, byte offset and line number to resume at, plus a hash of the search, so a cursor sent with another search is a 400. A cursor whose position cannot exist is a 400 too, and its file name is only matched against the recording's manifest, never joined into a path. Pages never go back, so a recording that starts behind the cursor while paging isn't searched. The paging rules are in `search.intent.md`. Issue: DGW-463 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
29adde5 to
01cf669
Compare
|
|
||
| Gateway keeps no state between pages; the cursor holds where the next page starts. | ||
| A cursor is accepted only with the same `query`, `recordings` and `startTime` as the page that gave it. | ||
| The hit that does not fit in a page opens the next one, so that every page moves the search forward and returns each hit once. |
There was a problem hiding this comment.
The intent is good, but I think one line could make the paging invariant explicit and remove ambiguity:
Page boundaries are only between hits (line results), never inside one.
If the next hit would exceed page limits, that hit is deferred to the next page.
This makes it clear that:
- pages advance strictly by whole hits,
- no line/hit is truncated to fit a page,
- and each hit appears exactly once across pages.

Searching every recording doesn't fit in one response, so
POST /jet/jrec/searchnow returns pages.A page ends after reading about 16 MiB of logs or after
maxHitshits, from 1 to 1000 and 1000 by default; any other value is rejected, never clamped.nextCursorreplaceslimitReached: sending the samequery,recordingsandstartTimewith it ascursorgets the next page, until a page has nonextCursor.Gateway keeps no state between pages. The cursor holds the recording, file, byte offset and line number to resume at, plus a hash of the search, so a cursor sent with another search is a 400. A cursor whose position cannot exist is a 400 too, and its file name is only matched against the recording's manifest, never joined into a path. Pages never go back, so a recording that starts behind the cursor while paging isn't searched. The paging rules are in
search.intent.md.🤖 Generated with Claude Code
Issue: DGW-463