@@ -404,6 +404,73 @@ Duplicate visible timestamps are made unique inside one query:
404404- backward scans decrement a duplicate timestamp (` systemd-journal-execute.h:173-179 ` );
405405- forward scans increment a duplicate timestamp (` systemd-journal-execute.h:282-288 ` ).
406406
407+ ## Query-Wide Anchor Contract (SDK Netdata Boundary)
408+
409+ SOW-0124 (2026-06-25) records the SDK Netdata function paging contract that
410+ matches the Netdata UI one-anchor assumption
411+ (` netdata/cloud-frontend @ b0f9c41cfc36 ` ):
412+
413+ - ` anchor ` is a microsecond timestamp scalar across all selected journal files,
414+ not a per-file offset. The UI stores one scalar ` anchorAfter ` /
415+ ` anchorBefore ` for the whole merged table and sends one scalar ` anchor ` on
416+ every load-more and tail poll. Consumers must treat it as an ordered scalar;
417+ they must not infer a per-file cursor from it.
418+ - The current wire shape exposes the anchor through `pagination.column =
419+ "timestamp"`. In the SDK this value is the Explorer row realtime, before the
420+ final same-file duplicate display adjustment: journal commit realtime unless
421+ an older ` _SOURCE_REALTIME_TIMESTAMP ` is present and selected as the effective
422+ row time. There is no separate hidden internal cursor or opaque page token in
423+ this contract.
424+ - Forward paging is strict: rows must satisfy ` realtime_usec > anchor ` to be
425+ eligible for the next page.
426+ - Non-tail backward paging is exclusive for all row queries, including
427+ ` data_only = true ` and ` data_only = false ` . The SDK converts a non-tail
428+ backward realtime anchor into an exclusive upper bound
429+ (` anchor - 1 ` microsecond) before Explorer range filtering, then clears
430+ the Explorer anchor slot. This prevents page 2 from re-fetching the
431+ boundary group that page 1 already returned.
432+ - Tail paging is exclusive on the next microsecond (` anchor + 1 ` ).
433+ - When a page boundary lands inside an equal scalar-timestamp group across
434+ files, the SDK retains the full boundary group from the merged per-file
435+ batches even though that may return more than the requested ` last ` . Returning
436+ more rows in that case is preferred over making the scalar anchor skip rows
437+ that share the boundary value, because the current wire contract cannot
438+ represent a compound cursor.
439+ - The SDK sorts combined per-file rows by pre-deduplication
440+ ` row.realtime_usec ` / ` Row.RealtimeUsec ` in the requested direction, with
441+ deterministic tie-breakers by file path and cursor. The combined retention
442+ uses the pre-deduplication timestamp at the ` limit - 1 ` index as the
443+ boundary. Backward pages retain every row whose timestamp is greater than or
444+ equal to the boundary. Forward pages retain every row whose timestamp is less
445+ than or equal to the boundary. Any same-file duplicate timestamp adjustment
446+ runs only after that boundary retention, so the cross-file boundary group
447+ stays intact.
448+ - Duplicate scalar timestamps across different journal files are preserved as
449+ equal in the response. Cross-file equal timestamps are the anchor collisions
450+ the scalar query-wide anchor must represent. Same-file duplicate timestamps
451+ still receive the existing direction-specific increment/decrement display
452+ adjustment as a small compatibility tweak inside one file only.
453+ - ` items.after ` / ` items.before ` semantics continue to indicate whether more
454+ rows are available; a page that returns more than ` last ` rows because of
455+ boundary-group retention must still report the correct remaining count
456+ rather than a false "no more rows" signal.
457+ - The implementation keeps per-file batched retrieval and a global merge.
458+ It does not switch to row-by-row k-way multi-file traversal. The user
459+ measured row-by-row k-way multi-file traversal as significantly slower
460+ than per-file batched query plus merge for many large sources, and the
461+ per-file batched shape remains the SDK performance contract.
462+ - The implementation does not introduce a new request key or response
463+ cursor token. The existing scalar ` anchor ` request shape and the existing
464+ response columns remain the wire contract.
465+
466+ The plugin's same-file duplicate display adjustment
467+ (` systemd-journal-execute.h:173-179 ` ,
468+ ` systemd-journal-execute.h:282-288 ` ) is still preserved as a small cosmetic
469+ display tweak when the boundary group is fully retained. The change is only
470+ that the SDK no longer mutates row timestamps before range/anchor filtering,
471+ no longer mutates duplicate timestamps across files, and no longer relies on
472+ per-file exclusive anchor semantics for non-tail backward queries.
473+
407474## File Selection And Traversal Order
408475
409476The plugin queries one journal file at a time. It never opens a multi-file
0 commit comments