Repository navigation
Add query boosts and optional full-text scoring - #82
Draft
kylebernhardy wants to merge 21 commits into
Draft
kylebernhardy wants to merge 21 commits into
kylebernhardy wants to merge 21 commits into
Conversation
This was referenced Oct 31, 2026
Open
18 tasks
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
Applications can require text matches or combine alternatives, but cannot reward an extra match without changing eligibility. Index field weights also cannot express a boost for one request.
Refs Extend typed queries with optional scoring clauses, boosts, regex, and similar-document search.
🤔 Is this the right first slice of query relevance controls?
Deliver boosts and required-plus-optional scoring first. These compose with existing text modes without rebuilding indexes. Regex, similar-document retrieval and a separate per-request field-weight map remain outside this slice; the linked issue stays open.
💡 Solution
Draft / release gate: reconcile the parallel telemetry ABI contract and choose an unused synchronized package version before publication. No release is performed by this PR.
boostmultiplies a subtree's score while preserving its matches.optionalrequires one expression and adds scores from matching optional expressions. Optional-only matches never enter the result.⚖️ Alternatives
🔧 Changes
Expression flow and compatibility
From request to result
The request keeps its required membership while optional matches contribute to ranking.
ts/native.ts exposes both expression variants, normalizes boosts once to float32, rejects conflicting shapes, and carries the normalized expression through search and continuation identity. Optional normalization captures an array length once for validation, budget charging and allocation, preventing Proxy-backed lists from expanding uncharged work. ts/codec.ts appends scoring tags while retaining tags 0–3 for existing leaves and Boolean groups.
src/protocol.rs decodes the new variants and accounts for their structure and admission class. src/request_validation.rs also validates direct Rust requests. Boosts must round to finite float32 values in
0..=100, excluding negative zero; optional lists must be nonempty. The shared native Optional-child check gives the Optional-specific diagnostic in typed validation, wire admission and decoding, while retaining Boolean diagnostics and error codes. Existing depth, byte, clause and deadline bounds apply to every child.src/lib.rs advertises native ABI 14 and query API 8; ts/load-addon.ts requires that matching addon. This changes request compatibility but not persisted index identity. The parallel Expose fulltext operation telemetry and latency histograms, verified at
4840b32a, also claims ABI 14 but query API 7 plus telemetry API 1. These incompatible contracts must be reconciled across native exports, runtime info, facade, loader, tests and docs before release. If telemetry ABI 14 ships first, scoring requires a fresh ABI and Harper compatibility-pair update; otherwise combine both unreleased contracts into one rebuilt artifact.Native scoring and preserved invariants
Mustbranch plus nativeShouldbonuses. The required branch alone decides eligibility for filters, candidate restrictions, counts and score/ID pagination. Only the required branch supplies a positive admission anchor; optional branches still contribute their expensive-work classification.Only root boosts over
Any/Allleaves multiply selected field weights before building term queries, preserving the query topology used by native pruning. Recursive boosts retain nativeBoostQuerymultiplication order: an inner large field weight must not overflow before an enclosing zero boost reaches it. Other root modes and complex subtrees also use nativeBoostQuerycomposition. At the root, this wrapper loses specialized Boolean pruning. Optional scoring over required All also adds work over the required-only intersection. Nested boosts multiply without a separate cumulative cap. Nonfinite result scores fail explicitly. Zero boosts preserve membership.Compiler-owned stable routing travels in
BuiltQuery.stable_scores. Term, Phrase, Fuzzy, prefix and Boolean builders determine it from the query they actually construct, replacing the duplicate AST-mode walk and division heuristic. Completed prefix/fuzzy-prefix and singleton exact-prefix paths can lower to the same faulty native conjunction; empty per-segment fields can also collapse unions into bare term scorers. Non-unit native Boost wrappers use generic scoring and clear the stable-routing flag; identity boosts inherit it, while candidate/filter wrappers keep their existing behavior. A four-term native canary compares raw TopDocs with scorer iteration. Stable collection scans matches and resolves IDs beyond the visible page. Both ordinary stable passes now reuse the shared deadline collector in src/catalog.rs, including the boundary-tie rerun. Its deletion-aware scorer loop checks the shared deadline every 1,024 visited documents and rejects the whole request on expiry. The engine error mapping preservesE_TIMEOUT; no partial hits or cursor escape. These cooperative checks do not preempt a slow individual scorer operation. src/DESIGN.md records remaining cost and native-bound limits; this is not a universal pruning repair or strict latency guarantee.Callers must authorize optional fields as well as required fields. An omitted field list searches every configured text field.
traceMatches()remains a simple-clause tracing API; boosts do not change spans.Evidence and caller guidance
test/query-scoring.test.mjs covers scoring, eligibility, restrictions, filtered count fallback, paging, validation and wire tags. Native engine/protocol/request tests cover typed Rust paths and scorer topology. New public compiled-query regressions compare bounded search with continuation scores at a distinct cutoff, including multiple segments, empty secondary fields, completed-prefix constructors and real candidate/filter restrictions. All and AND restriction controls each admit 150 of 300 documents. Two-term boosted All controls retain the native pruning path. The early-abort regression fails before the deadline fix by visiting all 4,096 matches; the fixed path aborts within 1,024 advances. Its deadline-enabled deletion control also preserves exact score bits and excludes tombstoned documents. Independent required-plus-two-bonus arithmetic rejects max-only scoring; a 127-record, varied-addend paging fixture catches real score-bit drift when stable routing is removed. Native nested-boost regressions fail before the fix for fractional weights and a large finite field weight under an outer zero boost. test/native.test.mjs and test/load-addon-resolution.test.mjs expect the new capabilities.
benchmarks/query-scoring.mjs adds single-term and two/three/four-term All baseline, root-lowered and native-wrapped controls. Paired correctness and score-bit checks precede timing; both default tied and varied modes complete 40 rows over four committed segments. The opt-in
--routing-shapesmode adds 20 rows per corpus for OR, singleton prefix, short Fuzzy, one-token two-field Phrase and three-term two-field All. Complete traversal reads respect prefix limits; timed requests use limit 20. package.json adds the benchmark command. The benchmark record distinguishes route-specific results from throughput, catalog-scale memory and general pruning guarantees.README.md and examples/query-modes.mjs show the caller contract; the README limits undefined-as-absent behavior to the listed scoring/structural properties, excluding present
text: undefined. docs/limits.md documents Rust enum migration and removal ofEq. DESIGN.md indexes the invariants in src/DESIGN.md.✅ Verification
Executed qualification results and environment limits
Latest
5394ecfchanges only the empty-Optional diagnostic and regression coverage; 163 Rust tests passed, 6 ignored, with Clippy, TypeScript, full formatting and diff checks passing. The regression failed before the fix and now covers all three native entry paths plus unchanged empty AND/OR messages. The scoring implementation remains2b48620. Previous-artifact Node qualification passed 467 main, 19 metadata and 12 worker tests, optimized scoring/control qualification 52 tests, and clean-environment release qualification 2 packed-consumer and 6 production-worker tests. No addon rebuild or Node-suite rerun is claimed for the diagnostic-only response.The deadline regression proves early cancellation instead of a post-scan error: it advances no more than 1,024 of 4,096 matching documents. A real deletion with merge disabled verifies live-document filtering, exact total 4,095 and exact score bits against the unwrapped stable collector. Existing four-term and public bounded-page regressions cover demonstrated compiler constructors, multiple segments, candidate restrictions and metadata filtering; they do not qualify every native rounding bound.
The final optimized addon SHA256 is
652d1aaafd045641c1880a6c08e1a52099df5aa599de91fabf64f1ec6191cd6b. Root and installed ABI 14/query API 8 artifacts match, and the ABI-13 backup is preserved. Canonicalnpm run test:nodereached the packed phase but npm 12 injectedEALLOWSCRIPTS; the direct canonical release runner passed after removing inherited allow-scripts variables. User npm configuration was unchanged.On this artifact, the unchanged default benchmark completed 40 rows per tied/varied mode, and opt-in routing shapes completed 20 rows per mode, each observing four committed segments. Varied exact-total-off p50 baseline/root/native-wrapped was All2 0.077/0.078/0.102 ms, All3 0.163/0.162/0.127 ms and All4 0.177/0.177/0.143 ms. Routing baseline/native-wrapper was OR 0.152/0.110 ms, prefix 0.159/0.129 ms, short Fuzzy 0.156/0.112 ms, Phrase over two fields 0.156/0.114 ms and All3 over two fields 0.251/0.227 ms. Membership, totals, scaled scores and distinct-boundary checks passed. These warm total-request measurements do not isolate pruning or deadline overhead and establish no general speedup or catalog-scale bound.
Harper's final paired deadline-aware
652dartifact passed 113 focused and 20 deployed tests, with the actual resolved addon hash verified. Local evidence:query-relevance-harper-deadline-final-focused.logandquery-relevance-harper-deadline-final-deployed.log. Hosted scoring CI remains gated on wrapper release and the Harper dependency pin/lock update.Persisted-index interoperability previously passed with real addons: ABI 13 seeded a document, ABI 14 reopened and added another, and ABI 13 reopened and read both. No persisted identity change or index rebuild is introduced.
Review response and stopping point
Five whole reviews preceded the focused Claude/domain delta at
5394ecf, which resolved the Optional diagnostic and earlier measurement findings. Final48e0fbfretains the historical benchmark baseline; its values were parent-checked without another outside review. Gemini authentication remains unavailable.The remaining package-version finding is a release gate. Extend typed queries with optional scoring clauses, boosts, regex, and similar-document search tracks native pruning cost, strict-property compatibility, the tied diagnostic mode and the telemetry collision. All five wrapper deferral markers are filed there. Its open issue was checked directly, without buying another code review solely to refresh bookkeeping.
Coordinated guide: Document full-text boosts and optional scoring, local head
c7e9a5b803aece59e702b20811757c1c514b50fa. Harper integration: Add full-text boosts and optional scoring conditions, local heada53592d9fa62df12d5a1366c5e948e188884bb78.Comment generated by kAIle (GPT-6)
Related PRs: #81 overlaps (verified ABI14/queryAPI7/telemetryAPI1 collision; reconcile with scoring14/8 before release: Expose fulltext operation telemetry and latency histograms), #79 overlaps (updates published-version guidance from Prepare fulltext 0.8.0 for release), #78 overlaps (extends request validation and admission from Expose fuzzy matching controls and align Rust request validation), #77 overlaps (extends structural budget paths from Make analyzed query capacity configurable per handle)
Complexity: complicated
Historical review context
The initial Grok findings below are superseded by the final response review at
2c3edf9: shape validation, native multiplication order, parity and benchmark evidence are addressed. Composite pruning remains an accepted measured tradeoff tracked in Extend typed queries with optional scoring clauses, boosts, regex, and similar-document search. The historical managed block is retained unchanged.Late Grok findings
4 surviving (highest major) — Grok reviewed 3db8a90, ruled against 358e1bb
ts/native.ts:1459— boost and optional expressions silently discard siblingclause/clausesproperties, so intended constraints never reach native searchsrc/engine.rs:1512— boosts around composite expressions still hide BooleanWeight pruning behind BoostQuery, making low-limit searches scan every matchREADME.md:574— the upgrade guide advertises query API 7/native ABI 13 although this build requires 8/14, leading operators toward incompatible artifactsts/codec.ts:1098— zero-accepting float validation retains an incorrect error message, alongside object stringification and compressed design proseNot standing: 6 resolved. Grok ran 17m (17m from launch to report); the fold adjudication (
cursor-grok@3db8a904fce8-8710a551, codex) took 2m.Deferred findings
3084f89c82b555f2none performance —src/engine.rs— Composite boosts bypass Tantivy specialized TopDocs pruning — filed Extend typed queries with optional scoring clauses, boosts, regex, and similar-document search #55e313f09f9410c6e0none correctness —README.md— Boolean groups reject leaf-only fields and mode properties — filed Extend typed queries with optional scoring clauses, boosts, regex, and similar-document search #55e33b81c1db8c0e17none performance —benchmarks/query-scoring.mjs— Default benchmark corpus retains score-boundary ties — filed Extend typed queries with optional scoring clauses, boosts, regex, and similar-document search #55e5b36a05fc329c13none correctness —ts/native.ts— Boolean groups reject formerly ignored leaf-only properties — filed Extend typed queries with optional scoring clauses, boosts, regex, and similar-document search #558703f3426d90022dnone reliability —package.json— ABI-14 source reuses the published 0.8.0 artifact coordinate — filed Extend typed queries with optional scoring clauses, boosts, regex, and similar-document search #55Late Grok findings
5 surviving (highest blocker) — Grok reviewed 5d809c9
package.json:75— a clean install resolves published ABI-13 native packages for an ABI-14 facade and fails addon loadingbenchmarks/query-scoring.mjs:25— the hot-path root-leaf boost specialization lacks a benchmark that exercises its specialized-pruning purposetest/query-scoring.test.mjs:344— root-boosted single-field All lacks public search-versus-traversal score parity coveragesrc/protocol.rs:1311— empty Optional frames report a Boolean-specific diagnostic through all native entry pathsREADME.md:510— the undefined-property rule overstates behavior because presenttext: undefinedstill rejectsNot standing: none. Grok ran 19m (19m from launch to report); the fold adjudication (
cursor-grok@5d809c96ad57-b4c84b62, codex) took 2m.Review-Coverage: authored=codex; ran=cursor-composer,claude,cursor-grok; late=cursor-grok:adjudicated; adjudicated=domain; blocked=gemini(auth); declined=muse; rounds=9; full=5 @ 48e0fbf
Review-Attention: deep ~25m (critical: codec.ts; decisions: do-less-alternative, stable-score-routing, conservative-three-term-cutoff, abi-without-version-bump) @ 48e0fbf