Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/branch-review-ledger.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -661,5 +661,7 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie
| 2026-08-05 | claude/design-system-1616-colors-aw538h | 98b65ae9f222e621da8b5bca75d0b0f25d05ca09 | prlanded | merged, squash 98b65ae verified content-identical to branch tip e66ecaa (empty diff) | vitest ckb-v2-token-contract (26 passed), vitest pwa-manifest (11 passed), live Playwright render check, CI green (pr-required) |
| 2026-08-05 | cursor/privacy-page-mockups-2ff6 | 7c82f92a447986178ba12d6f8b7a447bb63e91ef | Run PR sweep | resolved privacy/page conflict with #1621 standalone shell; fixed Devin double scroll-pad + scrollIntoView yank | merge resolved; prettier |
| 2026-08-05 | cursor/privacy-page-mockups-2ff6 | 32c4406cb728176933b669779d4f16fd245534bb | Run PR sweep | supersede: desktop index sticky top tracks measured StickySignalChrome height; prior row checks lacked decisive prettier output | prettier --check mockup+ledger: All matched files use Prettier code style!; ResizeObserver sticky chrome height for desktop index |
| 2026-08-06 | claude/implement-97vpz7 | 5ffa042d686a542de3333ebccbd903b6422124a7 | src/lib/rag/rag.ts, src/app/api/search/route.ts, tests/rag-unsupported-short-circuit-cache.test.ts (RAG soft-tail unsupported-short-circuit cache fix + corpus_grounding telemetry exposure) | PR #1646 opened (draft); no retrieval/ranking behaviour change; verified: lint, typecheck, full unit suite (513 files/5413 tests), eval:rag:offline, build, check:bundle-budget | lint,typecheck,test,eval:rag:offline,build,check:bundle-budget |
Comment thread
BigSimmo marked this conversation as resolved.
| 2026-08-06 | a24f74fdf0134487a03dce37dd9f1e9bd18502f5 | a24f74fdf0134487a03dce37dd9f1e9bd18502f5 | PR #1614 post-merge RAG index restoration audit | Pass - guard-only migration, no DDL, no ranking/RPC change; pr-policy ragRanking=false so no eval-canary required; 1 P3 doc nit (#248 renumber note says 237->246, row is #248) | check:migration-role; npx vitest run tests/supabase-schema.test.ts (74 passed); check:outstanding-issues |
| 2026-08-06 | PR #1614 / codex/restore-rag-indexes-20260804 | a24f74fdf0134487a03dce37dd9f1e9bd18502f5 | PR #1614 post-merge RAG index restoration audit | Pass - guard-only migration, no DDL, no ranking/RPC change; pr-policy ragRanking=false so no eval-canary required; 1 P3 doc nit (#248 renumber note says 237->246, row is #248); supersedes 2026-08-06 row (ref column mistakenly held commit SHA instead of PR ref, breaking ledger:lookup per Devin/Sentry review on PR #1636) | check:migration-role; npx vitest run tests/supabase-schema.test.ts (74 passed); check:outstanding-issues |
| 2026-08-06 | claude/implement-97vpz7 | 00ab7bfd34684bc854d15a3f28987674098a7130 | PR #1646 soft-tail answer-cache skip + soft-tail test hardening | fixed — answer-path soft-tail skip via rag-query-guard helpers; soft-tail fixture pins; duplicate memo test removed; in-corpus assert narrowed; budget 4362 | test:rag-query-guard+unsupported-cache+classifier-memo 22/22,check:maintainability-budgets 4362/4362 |
3 changes: 2 additions & 1 deletion scripts/check-maintainability-budgets.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,8 @@ const budgets = new Map([
["src/components/ClinicalDashboard.tsx", 4140],
// Evidence coverage, per-request hydration, and second-stage ranking live in
// focused rag modules; keep the reclaimed budget so it cannot silently drift back.
["src/lib/rag/rag.ts", 4351],
// 4362: soft-tail answer-cache skip call site (logic in rag-query-guard.ts).
["src/lib/rag/rag.ts", 4362],
["src/components/DocumentViewer.tsx", 1734],
["supabase/functions/indexing-v3-agent/index.ts", 2191],
]);
Expand Down
1 change: 1 addition & 0 deletions src/app/api/search/route.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -897,6 +897,7 @@ async function buildScopedSearchPayload(
weak_source_count: relevance.weakSourceCount,
retrieval_strategy: search.telemetry.retrieval_strategy,
retrieval_plan: search.telemetry.retrieval_plan,
corpus_grounding: search.telemetry.corpus_grounding,
Comment thread
BigSimmo marked this conversation as resolved.
smart_api_intent: smartApiPlan.intent,
smart_api_response_mode: smartApiPlan.responseMode,
smart_api_display_mode: smartApiPlan.displayMode,
Expand Down
1 change: 1 addition & 0 deletions src/lib/rag/rag-cache.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -542,6 +542,7 @@ export async function getSharedCachedSearch(
shared_cache_status: "hit",
shared_cache_miss_reason: null,
query_class: payload.telemetry?.query_class,
corpus_grounding: payload.telemetry?.corpus_grounding,
vector_candidate_count: payload.telemetry?.vector_candidate_count,
text_candidate_count: payload.telemetry?.text_candidate_count,
embedding_field_count: payload.telemetry?.embedding_field_count,
Expand Down
37 changes: 37 additions & 0 deletions src/lib/rag/rag-query-guard.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,3 +32,40 @@ export function isUnsupportedSoftTailAnalysis(query: string, analysis: ClinicalQ
if (clearlyNonClinicalConsumerPattern.test(query)) return false;
return analysis.confidence <= 0.42 && analysis.expandedTerms.length <= 5;
}

/**
* Soft-tail zeros should skip search/answer cache writes only when a nondeterministic
* classifier call could have produced them. Without an API key the classifier path is
* unreachable (`analyzeQueryWithClassifierFallback` returns early), and an
* `"out_of_corpus"` grounding verdict is a deterministic corpus-derived true negative —
* both stay cacheable.
*/
export function shouldSkipUnsupportedSoftTailCacheWrite(
query: string,
analysis: ClinicalQueryAnalysis,
options: {
openAiApiKeyPresent: boolean;
corpusGrounding?: ClinicalQueryAnalysis["corpusGrounding"];
},
): boolean {
if (!options.openAiApiKeyPresent) return false;
const grounding = options.corpusGrounding ?? analysis.corpusGrounding;
if (grounding === "out_of_corpus") return false;
return isUnsupportedSoftTailAnalysis(query, analysis);
}

/** Answer-path counterpart: only skip when the empty unsupported refusal came from the soft-tail short circuit. */
export function shouldSkipUnsupportedSoftTailAnswerCacheWrite(args: {
resultCount: number;
retrievalStrategy: string | undefined;
query: string;
analysis: ClinicalQueryAnalysis;
openAiApiKeyPresent: boolean;
corpusGrounding?: ClinicalQueryAnalysis["corpusGrounding"];
}): boolean {
if (args.resultCount > 0 || args.retrievalStrategy !== "unsupported_short_circuit") return false;
return shouldSkipUnsupportedSoftTailCacheWrite(args.query, args.analysis, {
openAiApiKeyPresent: args.openAiApiKeyPresent,
corpusGrounding: args.corpusGrounding,
});
}
35 changes: 30 additions & 5 deletions src/lib/rag/rag.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,6 +204,8 @@ export { retrievalPlanForQueryClass, type SearchChunksArgs, type SearchTelemetry
import {
clearlyOutsideCorpusMedicalPattern,
isUnsupportedSoftTailAnalysis,
shouldSkipUnsupportedSoftTailAnswerCacheWrite,
shouldSkipUnsupportedSoftTailCacheWrite,
unavailableDocumentNoisePattern,
} from "@/lib/rag/rag-query-guard";
export { shouldShortCircuitUnsupportedSearch } from "@/lib/rag/rag-query-guard";
Expand DownExpand Up@@ -951,6 +953,8 @@ type ClassifierVerdict = z.infer<typeof queryClassifierVerdictSchema>;
// a query's classification for the whole TTL. The full corpus-grounded relevance fix remains
// scoped to RAG optimisation Phase 2.
const classifierVerdictMemoTtlMs = 15 * 60 * 1000;
// Finding #11 follow-up: bounds retries for a rejected soft-tail verdict (isUnsupportedSoftTailAnalysis).
const rejectedSoftTailMemoTtlMs = 60 * 1000;
const classifierVerdictMemoMaxEntries = 500;
const classifierVerdictMemo = new Map<string, { expiresAt: number; verdict: ClassifierVerdict }>();
const classifierVerdictInflight = new Map<string, Promise<ClassifierVerdict>>();
Expand All@@ -970,12 +974,12 @@ function classifierVerdictMemoKey(query: string, analysis: ClinicalQueryAnalysis
}

/** Store classifier verdict memo. */
function storeClassifierVerdictMemo(key: string, verdict: ClassifierVerdict) {
function storeClassifierVerdictMemo(key: string, verdict: ClassifierVerdict, ttlMs = classifierVerdictMemoTtlMs) {
if (classifierVerdictMemo.size >= classifierVerdictMemoMaxEntries) {
const oldestKey = classifierVerdictMemo.keys().next().value;
if (oldestKey !== undefined) classifierVerdictMemo.delete(oldestKey);
}
classifierVerdictMemo.set(key, { expiresAt: Date.now() + classifierVerdictMemoTtlMs, verdict });
classifierVerdictMemo.set(key, { expiresAt: Date.now() + ttlMs, verdict });
}

/** Reset classifier verdict memo for tests. */
Expand DownExpand Up@@ -1152,7 +1156,10 @@ export async function analyzeQueryWithClassifierFallback(

try {
const verdict = await awaitWithCallerSignal(pending, opts?.signal);
storeClassifierVerdictMemo(memoKey, verdict);
// Finding #11 follow-up: bounded TTL for a rejected soft-tail verdict — see the constant above.
const rejected = verdict.confidence < 0.58 || verdict.queryClass === "unsupported_or_general";
const softTail = rejected && isUnsupportedSoftTailAnalysis(query, analysis);
storeClassifierVerdictMemo(memoKey, verdict, softTail ? rejectedSoftTailMemoTtlMs : undefined);
return applyClassifierVerdict(analysis, verdict);
} catch (error) {
if (
Expand DownExpand Up@@ -1740,7 +1747,14 @@ export async function searchChunksWithTelemetry(
telemetry.embedding_skip_reason = "unsupported_short_circuit";
telemetry.retrieval_strategy = "unsupported_short_circuit";
recordSearchScoreTelemetry(telemetry, []);
await setCachedSearch(args, [], telemetry, queryVariants, { indexingVersionAtRetrievalStart });
// Skip only when a reachable classifier could have produced a nondeterministic soft-tail zero.
if (
!shouldSkipUnsupportedSoftTailCacheWrite(retrievalQuery, queryAnalysis, {
openAiApiKeyPresent: Boolean(env.OPENAI_API_KEY),
})
) {
await setCachedSearch(args, [], telemetry, queryVariants, { indexingVersionAtRetrievalStart });
}
return finishSearch(searchTiming, { results: [] as SearchResult[], telemetry });
}

Expand DownExpand Up@@ -2947,7 +2961,18 @@ async function answerQuestionWithScopeUncoalesced(
},
});

if (answerRouteResultCanBeCached(routeDeadline))
// Soft-tail unsupported refusals must not stick in the 5-minute answer cache.
if (
answerRouteResultCanBeCached(routeDeadline) &&
!shouldSkipUnsupportedSoftTailAnswerCacheWrite({
resultCount: results.length,
retrievalStrategy: search.telemetry.retrieval_strategy,
query: answerFocusQuery,
analysis: queryAnalysis,
openAiApiKeyPresent: Boolean(env.OPENAI_API_KEY),
corpusGrounding: search.telemetry.corpus_grounding,
})
)
await setCachedAnswer(args, finalizedAnswer, { indexingVersionAtRetrievalStart });
routeDeadline.dispose();
return finalizedAnswer;
Expand Down
Loading
Loading