perf: pre-compute query phrases once per rankAnswerEvidence call - #99
Merged
BigSimmo merged 1 commit intoJun 29, 2026
Conversation
Extract buildQueryPhrases() from phraseScore() so the n-gram phrase array is built once per query rather than once per scored result. phraseScore() now accepts a pre-computed phrases[] instead of rebuilding it from tokens on every call. Before: rankAnswerEvidence scored N results, each calling phraseScore(tokens, haystack) which rebuilt the same phrase array each time — O(N × tokens²) redundant work. After: phrases are computed once in rankAnswerEvidence and passed to answerEvidenceScore → phraseScore — O(tokens²) total. Typical search scores 30–50+ results, making this a meaningful reduction in hot-path work for every search query.
Copilot created this pull request from a session on behalf of
BigSimmo
June 29, 2026 01:30
View session
BigSimmo
marked this pull request as ready for review
June 29, 2026 01:57
BigSimmo
deleted the
copilot/task-87357024-1243497866-2b1ccdf0-c938-4268-b3ae-7d15785bf550
branch
June 29, 2026 04:29
BigSimmo
pushed a commit
that referenced
this pull request
Jul 29, 2026
Codex review raised this as P1 on PR #1377 and it is correct. The L1-2 change started resolveSearchScope concurrently with the rate-limit RPC and aborted it on deny, on the claim that a throttled caller "still costs nothing". That claim is false. resolveSearchScope only returns without touching the database when there are no filters and no explicit document ids (search-scope.ts:242,253). With either present it enters the paginated `documents` loop at :269 plus the nested label loop. An AbortSignal cancels the client request; it does not un-execute a statement Postgres has already begun. `filters` is caller-controlled, so a throttled caller could keep spending database capacity while collecting 429s — the opposite of what admission control is for, and the wrong direction against capacity-review.md:106-113, which names Postgres CPU under concurrency the first soft failure. Scope now sits behind admission again. Two parts of the original change are kept because they are independent of the overlap and unambiguously correct: - `signal: request.signal` is threaded into resolveSearchScope, so a client disconnect finally cancels its paginated queries. search-scope.ts:200,328 always supported .abortSignal(...); this route never passed one. - the `scope` stage is still reported in Server-Timing. tests/answer-route-preamble.test.ts is inverted to the guard the reviewer asked for: no scope query may begin before the limiter admits, and a denied request (sent with filters, the shape that reaches the paginated loop) dispatches none at all. Both cases fail against the overlapping shape. The audit's L1-2 section and ledger #99 record the refutation so a later latency pass does not rediscover the overlap; re-attempting it requires a non-database admission gate ahead of the durable limiter. Verification: verify:cheap exit 0 on the merged tree (423 files, 4278 passed / 4 skipped). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF
BigSimmo
pushed a commit
that referenced
this pull request
Jul 29, 2026
Confirms the squash merge landed by content: the diff between squash commit f4a3f50 and branch tip bebc6c0 is empty, so nothing was orphaned by the auto-merge race despite four concurrent pushes during the merge window. Also records that the coverage follow-ups were renumbered #98/#99 -> #106/#107 (main claimed #98-#105 concurrently) with both rows verified intact, and that the three red `PR required` results were concurrency cancellations rather than defects. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YRCXgX4AWZ579bKN6sk6b
BigSimmo
pushed a commit
that referenced
this pull request
Jul 29, 2026
…iew batch Eight review findings (1 Codex P2, 7 CodeRabbit), each verified against the code before acting. MOST CONSEQUENTIAL — the L2-3 "recall is byte-identical" claim was wrong, and it was load-bearing. fetchDocumentTitleAliasRows (rag-candidate-sources.ts:482) applies .limit(12) with no ORDER BY, so which twelve documents return is plan-dependent; a new index can change the title-alias set feeding candidate assembly. "No query text changes" is true, but recall does not follow from it. That claim was the argument for keeping L2-3 out of canary territory, so the gating is revised: the documents-list and (status,id) uses stay ordering-safe, the RAG-path index is canary-gated unless the unordered .limit(12) is made deterministic first — the cheaper fix, since an unordered LIMIT is latent nondeterminism regardless of this work. Operator SQL alone never reaches staging, DR, or local replay: migrations/ is the source of truth and schema.sql only a mirror, so hand-run statements hit the live database and nothing else, and a required_indexes registration would fail on every replayed environment. Authoring the migration is now a required part of #102, following the 20260717170000 idempotent pattern. This PR still ships no migration (the #1312 objection), but the runbook no longer implies the operator sequence is sufficient. Test guard hardened: the ordering case anchored on a fixed 5 ms sleep, which can expire before the handler reaches the limiter. It now waits for consumeSubjectApiRateLimit to be entered, then asserts scope is untouched — the same guarantee without the timing fragility. Ledger: the 78e2beb record still described the reverted scope-vs-ratelimit overlap. The ledger is append-only, so this appends a superseding record via ledger:append --supersede rather than editing the row, per the contract. Status wording: #102 is "runbook prepared", not done, while the operator steps are pending; #105 separates shipped implementation from pending browser verification; #98/#99/#102/#103/#105 restored to the execution queue with their remaining actions. Fixed the MD038 malformed RAG-impact code span. Verification: verify:cheap exit 0 (427 files, 4386 passed / 4 skipped); check:branch-review-ledger pass (1232 records, no duplicates). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF
This was referenced Jul 30, 2026
15 tasks
BigSimmo
added a commit
that referenced
this pull request
Aug 24, 2026
… #JZM7RM, #KZJD4Q, #BJ80DB, #71NT23) (#2330)
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.
Summary
phraseScorerebuilt the same token n-gram array for every scored result. With 30–50+ results per query, phrase construction was O(N × tokens²); this moves it to O(tokens²) by computing once and passing down.Before:
After:
Verification
npm run verify:cheapnpm run verify:uiwhen UI, routing, styling, browser behavior, reduced-motion, or forced-colors behavior changednpm run verify:releasebefore release or handoff confidence claimsnpm run format:checknpm run check:production-readinesswhen clinical workflow, privacy, environment, Supabase, source governance, or deployment behavior changedClinical Governance Preflight
Complete this section when the change touches ingestion, answer generation, search/ranking, source rendering, document access, privacy, production env, or clinical output.
Clinical KB Database(sjrfecxgysukkwxsowpy)Notes
Array.from(queryTerms)× 4 allocations per result inbuildMatchExplanation; linearArray.includesmembership checks inevidence.ts; DELETE+INSERT vs upsert inreplaceSharedCacheRow;structuredCloneon every cache read; redundantcombinedrenormalization inresultTexts; eagerbuildClinicalTextSearchQueryinnormalizedCacheQuery; no explicit response compression config;imageMatchScorere-splits source on each token; linear cache-key scan ininvalidateRagCachesForOwner.