test(search): pin search retrieval-core round-trip budgets (#098), and retract a wrong #121 claim - #1464
Conversation
#121's evidence presented the in-session symlink bridge as the escape hatch for the Chromium 1194-vs-1234 mismatch. Observed today that it is conditional: in a Claude Code remote session the sandbox refused mkdir/ln -s under /opt/pw-browsers via the auto-mode classifier, not via file permissions — the directory is writable. A sandboxed session therefore cannot bridge the builds at all, and the honest options reduce to requesting the permission or declining to claim browser evidence. Recorded on #121 rather than as a new row: #121 already owns this condition, and the finding contradicts a claim inside it, so a separate row would both duplicate it and leave the overstated workaround standing. It also names the concrete consequence — the pre-paint guard designed on #130 cannot be written and proven in such a session. No new id allocated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018K7sEKH35KZkWxvCnQcNN2
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 17 minutes Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds an offline search round-trip budget suite, registers it as a required contract test, and updates the outstanding-issues ledger with revised evidence, gate details, and deletion-detection requirements. ChangesOffline search contract
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
Closes the half of #98 that PR #1450 left open: /api/search had no budget, so an added round trip there was still an inference rather than a red gate. tests/search-round-trip-budget.test.ts pins searchChunksWithTelemetry — the function src/app/api/search/route.ts actually calls — using the existing counting proxy, and is registered in both the offline contract fixture and rag-offline-contract.mjs so it runs in the contract rather than on demand. Two scenarios. A lexical clinical search currently costs 11 round trips: rag_aliases 1, match_document_chunks_text_v2 3, match_document_table_facts_text_v2 3, get_related_document_metadata_v2 1, document_index_quality 1, document_images 2. Both the total and the breakdown are pinned, because a refactor swapping one probe for an unrelated query would hold the total while changing the traffic. Whether 3+3 text RPCs per search is intended is not settled here — the budget makes it visible and #98 now carries it as an open retrieval decision. The second scenario pins zero Supabase traffic for a query refused as adversarial, matching rag.ts's claim that prompt-injection intent is refused before any query issues. Both guards were proven against the broken shape rather than assumed: - The first control spent 0 trips because the module registry was cached between runs, so it would have passed while proving nothing. Fixed with vi.resetModules() inside the harness. - The refusal check initially failed on the results assertion rather than the round-trip one, demonstrating only that the test catches the regression, not that the budget can fail. The counter assertion is now ordered first; with a non-refused query it fails as "expected 11 to be +0". Counts are deterministic across three consecutive runs. No src/lib/rag file is edited; this observes the path, it does not change retrieval behaviour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018K7sEKH35KZkWxvCnQcNN2
…uin' into claude/understand-next-steps-tvxuin
Earlier today I recorded on #121 that a sandboxed remote session has only two options for the Chromium 1194-vs-1234 mismatch: get the /opt write permission, or decline to claim browser evidence. That was an over-generalisation from a single blocked mkdir, and it is wrong. PR #1432 landed a preflight whose own failure message names the route I had missed: PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, read by playwright.config.ts:11 and honoured by scripts/playwright-browser-preflight.mjs:101. It needs no filesystem write. Verified by launching rather than by reading the flag — the container's existing 1194 headless_shell drives fine under the repo's Playwright 1.62 client (version 141.0.7390.37, page rendered, boundingBox measured). #121 keeps the wrong sentence with the retraction beside it, because which claim was wrong and why is the part worth carrying forward. #130's recorded blocker for the pre-paint guard is marked LIFTED: that guard is buildable in a remote session after all. Also records #1432 itself, which no row referenced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018K7sEKH35KZkWxvCnQcNN2
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 66f4872d5a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…t-steps-tvxuin # Conflicts: # docs/outstanding-issues.md
…t-steps-tvxuin # Conflicts: # docs/outstanding-issues.md
…point Codex review was right. The suite invokes searchChunksWithTelemetry directly, so it is a budget for the search retrieval core, not for /api/search. Everything the route does around retrieval — auth, rate limiting, scope resolution, related-document enrichment, the telemetry write — is invisible to it, and a round trip added to any of those leaves the suite green. The refusal budget is likewise about retrieval: an adversarial HTTP request still pays the route's preamble before retrieval is reached, so "zero round trips" was true of the function and false of the endpoint. Nothing about the measurements changes; the claims around them do. The file header, describe block, both test names and the refusal assertion now say retrieval core, and the header states what is NOT covered so the suite cannot be cited as endpoint coverage. #98 carries the same correction rather than leaving the overstated version in the ledger. The stronger remedy Codex offered — drive POST with counted clients — is filed as #98's next step with the tests/answer-route-preamble.test.ts pattern named, rather than half-built here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018K7sEKH35KZkWxvCnQcNN2
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/rag-offline-contract.mjs`:
- Around line 26-28: Update the comment near
tests/search-round-trip-budget.test.ts to describe the guard as covering the
retrieval core, specifically searchChunksWithTelemetry, rather than /api/search.
State that it verifies adversarial queries avoid Supabase queries, without
implying coverage of endpoint authentication, rate limiting, scope resolution,
enrichment, or telemetry writes.
In `@tests/search-round-trip-budget.test.ts`:
- Around line 139-143: Update the afterEach cleanup in
search-round-trip-budget.test.ts to explicitly remove the vi.doMock
registrations for "`@/lib/supabase/admin`" and "`@/lib/openai`" using vi.doUnmock,
alongside the existing restoreAllMocks, resetModules, and unstubAllEnvs calls.
🪄 Autofix (Beta)
❌ Autofix failed (check again to retry)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 410f9849-dae3-4307-a258-bc68217f73f2
📒 Files selected for processing (4)
docs/outstanding-issues.mdscripts/fixtures/rag-offline-contract-tests.jsonscripts/rag-offline-contract.mjstests/search-round-trip-budget.test.ts
Both findings verified against the code before accepting, and both were right. 1. scripts/rag-offline-contract.mjs still described the suite as "the same guard for /api/search". That is the same overstatement Codex caught in the test and the ledger, and I had missed this third copy of it. The comment now says retrieval core and names what the suite does not observe: the route's auth, rate limiting, scope resolution, enrichment and telemetry write. 2. vi.doMock registrations survive vi.restoreAllMocks (which targets spies) and vi.resetModules (which clears the module cache, not the mock registry), so the Supabase/OpenAI fakes could outlive this file if isolation were relaxed. afterEach now calls vi.doUnmock for both. This is the established pattern here rather than a generic suggestion: five sibling suites already do it, and the comment cites tests/rag-variant-early-exit.test.ts:110. Also resolves the fourth docs/outstanding-issues.md conflict from main advancing, by rebuilding the #98/#121/#130 edits on main's table and diffing row-id sets against main to prove nothing was lost. Focused suites after the change, including the neighbouring mock-sensitive ones: Test Files 3 passed (3), Tests 9 passed (9). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018K7sEKH35KZkWxvCnQcNN2
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. Autofix skipped. No unresolved CodeRabbit review comments with fix instructions found. |
Summary
Budget the search retrieval core (
#098).tests/search-round-trip-budget.test.tspins Supabase round-trip counts forsearchChunksWithTelemetry— the functionsrc/app/api/search/route.tscalls to retrieve — registered in bothscripts/fixtures/rag-offline-contract-tests.jsonandscripts/rag-offline-contract.mjsso it runs inside the offline contract.Scope, corrected after review — this is not a budget for the
/api/searchendpoint. Earlier versions of this PR, of ledger#098, and of the contract-runner comment all claimed it was. It is not: the route's authentication, rate limiting, scope resolution, related-document enrichment and telemetry write are invisible to this suite, so a round trip added to any of them leaves it green. The refusal scenario likewise says retrieval spends nothing on an adversarial query — an adversarial HTTP request still pays the route preamble. The file header,describe, both test names, the refusal assertion message, the#098row and the contract-runner comment all now say retrieval core and name the exclusions. DrivingPOSTwith counted clients is filed as#098's next step, namingtests/answer-route-preamble.test.tsas the pattern to copy.The measured shape is itself a finding. One retrieval costs 11 round trips:
rag_aliases1,match_document_chunks_text_v23,match_document_table_facts_text_v23,get_related_document_metadata_v21,document_index_quality1,document_images2 — both text RPCs firing three times. Pinned by total and breakdown, since a refactor swapping one probe for an unrelated query would hold the total at 11 while changing the traffic. Whether 3+3 is intended is not established here — a retrieval question needing the RAG gate, carried on#098as an open decision rather than a guess.Retract a claim I published earlier today on
#121. An earlier version of this PR recorded that a sandboxed remote session has only two options for the Chromium1194-vs-1234mismatch — obtain/optwrite permission, or decline to claim browser evidence. That was wrong, an over-generalisation from a single blockedmkdir. PR fix: Playwright browser preflight prevents #120 false greens #1432's preflight names the route I missed:PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, read byplaywright.config.ts:11, needing no filesystem write. Verified by launching rather than by reading the flag — the container's existing1194headless_shelldrives fine under the repo's Playwright 1.62 client (version 141.0.7390.37, page rendered,boundingBoxmeasured). The wrong sentence is kept on the row with the retraction beside it, because which claim failed and why is the part worth carrying forward.Mark
#130's recorded blocker LIFTED. The pre-paint/cold-load guard was recorded as unbuildable in a remote session; on the corrected facts it is buildable. Also records PR fix: Playwright browser preflight prevents #120 false greens #1432 itself, which no ledger row referenced —assertPlaywrightBrowsersReadynow runs insidescripts/run-playwright.mjs, so a missing binary exits1with one explicit message instead of surfacing as N tests "failing" at launch, the misdiagnosis that produced#120.Verification
npm run verify:cheapon the final tip — exit 0,Test Files 443 passed (443),Tests 4627 passed | 4 skipped (4631).includeistests/**/*.test.tsandvitest listreports the file with both tests. "443 passed" alone does not prove a specific file ran.npm run check:rag:fixtures—Offline RAG fixture and manifest validation passed (36 golden cases, 23 suites).npm run check:outstanding-issues—146 rows (57 open, 89 archived), unique ids, next-id=149 above the highest.npm run format:checkwhole tree,npm run typecheck,eslinton touched files — clean. Prettier reformatted the new test after its first commit, so that commit was amended rather than pushed unformatted; CI checks the pushed blob, not the working tree.Tests 2 passed (2)each, before any number was pinned.expected 11 to be +0). A first attempt failed on the results assertion instead, which would have proved only that the test catches the regression, not that the budget can fail — so the counter assertion is deliberately ordered first.vi.resetModules()now runs inside the harness, with the reason commented at the call site.Test Files 3 passed (3),Tests 9 passed (9)across this suite, the answer-path budget, andtests/rag-variant-early-exit.test.ts— the neighbouring mock-sensitive suite, run because thevi.doUnmockchange concerns exactly that risk.UI verification not run: no UI, routing, styling, or motion content. Browser evidence is now possible here via
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH— that is what this PR retracts — but nothing in this diff requires it.Verification not run: every provider-backed gate (
eval:*,verify:release,check:supabase-project,test:live) is unimplicated and unauthorized; no OpenAI, Supabase, or hosted-CI call was made. The suite is provider-free and DB-free, counting traffic through a mocked@/lib/supabase/adminclient.Risk and rollout
src/lib/rag/**file is touched — the search surface is observed, not modified.fetch, or a provider SDK is invisible.git revertthe relevant commit. Reverting the test removes a gate; it changes no product behaviour.RAG impact: no retrieval behaviour change — this PR adds a test that observes
searchChunksWithTelemetrythrough a mocked Supabase client, plus its registration in the offline contract list. No retrieval, ranking, selection, alias, scoring or citation code is modified, no index is applied, and no fixture case or comparator ordering is changed. The recorded3+3RPC observation is a measurement of existing behaviour, left as an open question on#098rather than acted on.Clinical Governance Preflight
Clinical KB Database(sjrfecxgysukkwxsowpy)Notes on the above, stated plainly rather than implying an inspection that did not happen: every item is satisfied by construction, because this PR adds a test and ledger prose and modifies no product code.
scripts/pr-policy.mjsclassifies the diffclinicalRisk: false, so this section is not required by the repo's own gate; it is included because the change sits adjacent to retrieval and an automated description check asked for it. No answer generation, citation, verification, source-governance or document-access code is touched. The only Supabase client involved is a mock inside the test; no credential, key or project value appears in the diff, and the pinned project ref is referenced only as unchanged context. Clinical decision-support behaviour is unaffected, so the SaMD classification is unchanged. The one clinically-relevant effect is indirect and protective: an added round trip on the search retrieval path now fails a gate instead of going unnoticed.Notes
c24116a1, both verified against the code before being accepted. The contract-runner comment was a third copy of the/api/searchoverstatement I had missed. Thevi.doUnmockcleanup is the established pattern here, not a generic suggestion — five sibling suites already do it, and the added comment citestests/rag-variant-early-exit.test.ts:110.mainsyncs plus a merge of a concurrent push by another actor. Four produced real conflicts indocs/outstanding-issues.md, each resolved by rebuilding the edits onmain's table and diffing the row-id sets againstmainto prove nothing was lost — never by taking one side wholesale. Nothing was force-pushed.main's own Stop Prettier padding the issues ledger table, closing #133 #1479 has since stopped Prettier padding that table, which should make such conflicts rarer.🤖 Generated with Claude Code
https://claude.ai/code/session_018K7sEKH35KZkWxvCnQcNN2