fix(search): stop /api/search/universal serving demo docs to live public users#373
Conversation
…lic users
The universal typeahead route short-circuited anonymous (no-cookie) and
owner-less callers to runUniversalSearch({ demo: true }), which serves the
synthetic demo corpus (demoSearch) for the documents domain instead of the
real indexed public corpus. In production isDemoMode() is false, so every
unauthenticated visitor hitting the typeahead was shown demo documents while
/api/search and /api/answer served them the real public corpus.
Drop both demo short-circuits so non-demo callers always run the live
pipeline (demo:false + admin client). Anonymous callers resolve to ownerId
undefined -> allowGlobalSearch, scoping the documents domain to the public
corpus (owner_id IS NULL) exactly like /api/search; registry domains already
used the real default catalogues. The anonymous path is now rate limited (it
previously bypassed the limiter). Only an explicit demo/local deploy still
serves fixtures.
Adds regression tests: an anonymous live request runs demo:false against the
public corpus, the anonymous path is rate limited, and the owner path is
unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Follow-up: structural guard so this class can't recurAdded a second commit that closes the whole demo-served-live class, not just this route.
Mirrors the existing fail-closed Safety note also verified for reviewers: the anonymous path uses |
Summary
/api/search/universal(the cross-entity typeahead) short-circuited anonymous / no-owner callers torunUniversalSearch({ demo: true }). For the documents domain that serves the synthetic demo corpus (demoSearch) instead of the real indexed public corpus. BecauseisDemoMode()isfalsein production, every unauthenticated visitor using the typeahead was shown demo documents, while/api/searchand/api/answeralready served them the real public corpus.This drops both demo short-circuits so non-demo callers always run the live pipeline (
demo:false+ admin client):ownerId: undefined→allowGlobalSearch, scoping the documents domain to the public corpus (owner_id IS NULL) — the same pattern/api/searchuses. Registry domains (medications/services/forms/differentials/tools) already served the real default catalogues, so those are unchanged.isDemoMode()/isLocalNoAuthMode()) still serves fixtures.Anonymous responses keep the
publicAccess: trueflag; owner responses are unchanged.Regression tests added in
tests/universal-search.test.ts: an anonymous live request runsdemo:falseagainst the public corpus (not fixtures), the anonymous path returns 429 when throttled, and the authenticated owner path is unchanged.Verification
npx tsc --noEmit(typecheck) — cleannpx eslint+npx prettier --checkon changed files — cleannpx vitest runonuniversal-search+ access-contract suites (api-route-coverage,public-access-deep,private-access-routes,registry-records-route) — 140/140 passnpm run verify:cheap/check:production-readiness— not run here: this worktree has a partialnode_modulesand no live Supabase keys ([demo mode]). Recommend CI + a maintainer with live keys run these.eval:retrieval:quality— not applicable: no retrieval/ranking/selection/chunking/scoring logic changed. This is an access-routing fix; the anonymous path now uses the same live pipeline/api/searchalready runs.Clinical Governance Preflight
Clinical KB Database(sjrfecxgysukkwxsowpy) — untouchedNotes
registry-bucket rate limit bounds cost/abuse on that path.main.🤖 Generated with Claude Code