Repository navigation
Add bounded snapshot traversal for full-text and autocomplete results - #67
Merged
Merged
Conversation
5 tasks
kylebernhardy
marked this pull request as ready for review
October 18, 2026 17:05
This was referenced Oct 18, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
⊙ Problem
Offset search cannot reach matches after position 10,000. Autocomplete returns at most 100 raw hits, so a host that rejects those hits can miss eligible matches later in the ranking. Add bounded deep-result traversal and qualify autocomplete limits owns this native capability; Support full-text and autocomplete continuation in Table.search and REST owns the separate Harper integration.
💡 Solution
Add
searchPage()to index and reader facades for ordinary, prefix, fuzzy-prefix and Boolean queries. Pages use score-descending / UTF-8 ID-ascending order, exact snapshot totals and authenticated continuations. Each page is bounded independently of the total traversable matches. Existingsearch()windows remain compatible.⚖️ Alternatives
Ordinary searches reuse their packed request instead of copying it for traversal. One small encoder metadata object remains; the reviewer explicitly accepts that measured-path tradeoff because the local samples do not resolve its cost.
Use an existing dedicated read-only handle rather than a new native cursor registry. The caller owns its lease and closes it. Raising the offset window would still retain winners proportional to depth; ID-ordered export would change ranking. The chosen collector rescans matches each page, approximately N²/P visits for N matches and page size P. Reduce repeated matching work during deep ranked traversal tracks a separately measured optimization.
🔧 Changes
Product and architecture tour
Ranked traversal and snapshot ownership
Keep the reader open and do not reload it between pages. Writer publications and merges can proceed while the reader serves its original indexed snapshot. Reload onto a changed snapshot yields
E_CURSOR_EXPIRED; same-snapshot reload preserves continuations. Writer-handle continuation is also supported but publication can expire it. Tokens cannot move to another facade, process or reopened handle. Source records and permissions remain the host's responsibility; this library snapshot does not freeze authoritative source data.Ranked traversal request flow
Each page uses the retained snapshot and returns an authenticated boundary.
flowchart LR Q[Query and continuation] --> F[Facade validation and preparation] F --> N[Existing native search entry point] N --> A[Expensive-search admission] A --> S[Retained reader snapshot] S --> C[Bounded search-after collector] C --> R[Hits and exact raw total] R --> T[Authenticated continuation]The traversal collector retains page-size-plus-one candidates per segment and applies the exclusive score/ID boundary. The ordinary stable collector stays independent. Matching-loop deadline checks can stop traversal; individual Tantivy query/scorer operations remain uninterruptible.
Token integrity and bounded preparation
Version-3 tokens authenticate version/handle, snapshot, score, ID and fixed query digest using a 32-byte per-object HMAC key. Snapshot identity is SHA-256 over an explicitly framed, ordered Tantivy segment-ID/deletion-generation map. The engine derives it from the captured searcher for both direct Rust and Node callers, without mutable lease state. Maximum encoded continuations are 5,602 characters; older unreleased token versions reject explicitly. Constant-time comparison rejects finite boundary edits, splicing, query/reader mismatch and old tokens. Page size, exact-total preference and execution budget remain outside query identity as documented. Integrity does not grant authorization.
Large-hash preparation respects configured search queue limits and ceilings derived from the 8 MiB request bound. Reservations release before native submission; remaining time and close state are rechecked after hashing. Preparation-specific
E_QUEUE_FULLmessages distinguish this stage from native saturation; native status counters exclude it. WebCrypto shares libuv capacity and may copy input, so configured frame bytes are not total RSS guarantees.Capacity and compatibility
The package prepares 0.6.0 with native ABI 9 / query API 4. Direct Rust
SearchRequestliterals now requiretraversalandafter;Engine::searchenforces the checked 10,000-hit offset window. Rust deep traversal usestraversal: trueon the same method and retains the original searcher. The draft token format changes within that unreleased version set; generated addon declarations remain unchanged. No analyzer or source-index migration is introduced. Checked ordinary-window validation is shared by decoding and direct Rust execution. Search responses reject computed non-finite scores before exposing hits/tokens. Existing term/clause, exact-prefix expansion, candidate, byte and execution ceilings remain unchanged; Make query term and clause limits configurable with bounded resource policy and Qualify higher candidate, prefix-expansion and payload capacity profiles track separate policy work.Changed-file coverage
| CONTRIBUTING.md | Match the supported Node versions and document synchronous encoding and identity hashing. |
| docs/native-checkpoint-publication.md | Link checkpoint rules to their implementation. |
| docs/native-reset-quiescence.md | Separate historical close behavior from current teardown; clarify other-reader ownership and close error recovery. |
| docs/release-packaging.md | Use a direct release-steps heading. |
| docs/terminal-writer-admission.md | Mark the former dirty-close behavior as historical and link source and regression tests. |
✅ Verification
Documentation accuracy pass: checked public types, numeric bounds, preparation, close and reset against source; independent Gemini document review found no actionable corrections. Local Markdown targets,
npm run format:check, andgit diff --checkpass. This follow-up changes documentation only.npm run format:check,npm run lint, andNPM_CONFIG_USERCONFIG=/dev/null npm test: passed; 250 Rust/Node/worker/release/offline-consumer checks. The npm override is process-local to avoid this machine's npm script policy; user configuration is unchanged.cargo deny checkpassed advisories, bans, licenses and source policy;npm audit --audit-level=highreported zero vulnerabilities.SHA-256 preimage vectors were independently calculated with Python. Native tests distinguish absent deletion generations, generation zero, later deletes and segment replacement. The public token-version assertion fails before correction and passes afterward.
npm run test:optimized-native: two passing phrase/traversal contention tests, including native queue saturation and capacity recovery.Public boundary-tampering and preparation-admission regressions fail before the fixes and pass afterward. Coverage includes finite score/ID/snapshot edits, splicing, old tokens, concurrent initial tokens, large-identity command/byte pressure, real crypto-pool timeout, close and subsequent recovery.
Deep traversal checks original versions beyond 10,000 hits during publications; autocomplete checks hits beyond 100, mixed scores/UTF-8 ties, Boolean/filter/candidate shapes, retained snapshots and explicit 50/51-term expansion outcomes. Independent Rust collector equivalence and forced-merge/deadline tests pass.
Full and smoke traversal qualification passed locally with large IDs/versions and concurrent writes/ordinary searches. One full sample used 16,384 records / 12 actual segments. Platform CI runs the smaller correctness profile; Linux/Windows results remain pending, not a local success claim.
The native SHA-256 follow-up qualification compared identical 512-record fixtures at exactly 1 and 256 segments, page sizes 1 and 32, against pre-SHA
f9ec4a4. Three alternating optimized native process pairs verified every scored sequence and total; 256-segment paired full-walk changes were +0.8–2.8% at size 1 and +1.3–2.3% at size 32. These local observations are an accepted integrity cost, not an isolated hash measurement or production bound. The ignored qualification has an explicit reproduction command in the limits guide.One optimized macOS arm64 / Node 24 large-query before/after sample measured synchronous submission median 5.72 → 3.12 ms and completion median 321 → 325 ms. The benchmark includes encoding and native work: this shows reduced synchronous preparation, not an overall search speedup or production SLO.
No companion Harper documentation PR is needed for this standalone package change: its API documentation is in this package's README and limits guide. Harper product API/documentation follow its separate integration issue; no live Harper end-to-end result is claimed. The independent review's stale backend contract and 64-bit fingerprint findings are addressed by corrected execution/cancellation documentation and SHA-256 snapshot identity. Corrective review and the closing full review by Claude and Cursor Composer, with Harper domain adjudication, completed at
7bdb596. The original documentation/fingerprint findings and the many-segment performance-qualification follow-up are resolved. The final post-review delta documents the Rust 0.6 compatibility changes and permanent queue-capacity cases, and removes one redundant assertion; it changes no production behavior, passes all 250 checks, and was not independently code-reviewed. A subsequent documentation-only pass corrects the copied API types and reader-lifecycle wording, simplifies the traversal explanation, and adds source links; its independent document check found no actionable corrections. The 48-byte header increase is an accepted, documented change within the unreleased token format. Package publication and Harper integration qualification remain open; platform CI must rerun on the updated head.Comment generated by kAIle (GPT-6)
Release target: main — prepare 0.6.0; no backport requested.
Related PRs: #48 independent
Complexity: complicated
Review-Coverage: authored=codex; ran=claude,cursor-composer; adjudicated=domain; blocked=gemini(auth); declined=cursor-grok,cursor-kimi,cursor-muse; rounds=17; full=11 @ 104cf5b
Review-Attention: deep ~35m (critical: codec.ts; decisions: do-less-alternative, stateless-snapshot-identity, exact-total-every-page, hybrid-hash-scheduling, rust-api-break) @ 104cf5b