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
1 change: 1 addition & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie
| 2026-07-30 | PR-1494 | 807a3a09f5afc12e8db4f9158abe09d9c7b336c9 | PR #1494 pre-commit fail-open review | FIXED P2: legacy worktrees may skip a genuinely absent generator, while a staged deletion or rename now fails closed | docs-inventory Vitest 5 passed; shell syntax passed; Prettier test check passed; diff check passed |
| 2026-07-30 | claude/x3-rag-coverage-gate-qx9j7d (PR #1463, squashed as dba7356f) | dba7356fc8dc926d951d6de6f019d5b8e000be21 | X3/#101 per-request hydration extraction from rag.ts into rag-hydration.ts | clean and landed — byte-identical move verified against pre-merge main, rag.ts 4780->4543, budget ratcheted to 4543, no back-edge (cluster referenced zero rag.ts-local symbols), both public re-exports preserved; squash captured 100% of branch content | typecheck, lint, check:knip, check:maintainability-budgets 4543/4543, focused vitest 83/83 incl rag-query-concurrency, eval:rag:offline 572/572 36 golden, format:check, verify:cheap, verify:pr-local build+bundle-scan, post-merge content verification on main |
| 2026-07-30 | dba7356fc8dc926d951d6de6f019d5b8e000be21 | dba7356fc8dc926d951d6de6f019d5b8e000be21 | X3 hydration unit: per-request hydration extraction from rag.ts into rag-hydration.ts (PR #1463) | clean and landed — byte-identical move verified against pre-merge main, rag.ts 4780->4543, budget ratcheted to 4543, no back-edge, both public re-exports preserved. Supersedes the earlier row for this HEAD, which was keyed only to the slash-form branch token and so returned NOT REVIEWED on a landed-SHA lookup; it also mislabelled the unit as #101, which is the unrelated open canary-gated retrieval-parallelisation recommendation | typecheck, lint, check:knip, check:maintainability-budgets 4543/4543, focused vitest 83/83, eval:rag:offline 572/572 36 golden, format:check, verify:cheap 442 files 4625 passed, verify:pr-local, post-merge content verification on main |
| 2026-07-30 | PR #1474 | 098186866932394d2cc17983e566ae6c44b063b4 | PR #1474 full diff vs origin/main | approved | verify:cheap; eval:rag:offline; live canary 30578169116 -> 30579534353 |
| 2026-07-30 | codex/coverage-scope-policy | 94f97cdb1d0543724de408f19e79d64e61c8b31a | issue 139 coverage scope policy | approved: workflow coverage breadth is deliberate and test-pinned; docs-like skills remain static-only | check:ci-scope; check:gate-manifest; check:outstanding-issues; prettier; diff check |
| 2026-07-30 | codex/coverage-scope-policy | 4da2a003bc2254507662d1b8b6e9768e94371abd | issue 139 coverage scope policy post-sync | approved: late main sync preserves deliberate workflow coverage and static-only skill policy | check:ci-scope; check:outstanding-issues; check:branch-review-ledger; diff check |
| 2026-07-30 | codex/archive-completed-ci-tasks | 5c902f422ceee78ef68132900fda734c1d5bc1f8 | archive issues 133 and 135 | approved: both rows were already resolved on current main and focused guards prove their contracts | check:ci-scope; check:outstanding-issues; check:branch-review-ledger; diff check |
Expand Down
46 changes: 35 additions & 11 deletions src/lib/rag/rag-candidate-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1166,20 +1166,22 @@ export async function searchIndexUnitCandidates(args: {

export type MemoryCardCache = Map<string, ReturnType<typeof fetchMemoryCardsForQuery>>;

/** With memory boosted candidates. */
export async function withMemoryBoostedCandidates(args: {
export type MemoryBoostArtifacts = {
cards: Awaited<ReturnType<typeof fetchMemoryCardsForQuery>>;
memoryChunkResults: SearchResult[];
};

/** Load the memory evidence that is independent of the current candidate payload. */
export async function loadMemoryBoostArtifacts(args: {
supabase: ReturnType<typeof createAdminClient>;
query: string;
candidates: SearchResult[];
queryEmbedding?: number[];
ownerId?: string;
accessScope?: RetrievalAccessScope;
documentIds?: string[];
matchCount: number;
cardCache?: MemoryCardCache;
}) {
// A3: the memory-card fetch is invoked at several waterfall stages. Memoize per request,
// scoped by owner/document filters because fetchMemoryCardsForQuery applies those filters.
}): Promise<MemoryBoostArtifacts> {
const effectiveMatchCount = Math.max(args.matchCount, 48);
const documentScope = args.documentIds?.length ? [...args.documentIds].sort().join(",") : "all-documents";
const cacheKey = [
Expand All @@ -1203,12 +1205,34 @@ export async function withMemoryBoostedCandidates(args: {
args.cardCache?.set(cacheKey, cardsPromise);
}
const cards = await cardsPromise;
if (cards.length === 0) return { results: args.candidates, cards };
const memoryChunkResults = cards.length
? await loadChunksForMemoryCards(args.supabase, cards, retrievalAccessScopeForArgs(args))
: [];
return { cards, memoryChunkResults };
}

const memoryChunkResults = await loadChunksForMemoryCards(args.supabase, cards, retrievalAccessScopeForArgs(args));
const merged = mergeSearchResults(memoryChunkResults, args.candidates);
/** Apply already-loaded memory evidence to the final hydrated candidate payload. */
export function applyMemoryBoostArtifacts(query: string, candidates: SearchResult[], artifacts: MemoryBoostArtifacts) {
if (artifacts.cards.length === 0) return { results: candidates, cards: artifacts.cards };
const merged = mergeSearchResults(artifacts.memoryChunkResults, candidates);
return {
results: applyMemoryCardBoosts(args.query, merged, cards),
cards,
results: applyMemoryCardBoosts(query, merged, artifacts.cards),
cards: artifacts.cards,
};
}

/** With memory boosted candidates. */
export async function withMemoryBoostedCandidates(args: {
supabase: ReturnType<typeof createAdminClient>;
query: string;
candidates: SearchResult[];
queryEmbedding?: number[];
ownerId?: string;
accessScope?: RetrievalAccessScope;
documentIds?: string[];
matchCount: number;
cardCache?: MemoryCardCache;
}) {
const artifacts = await loadMemoryBoostArtifacts(args);
return applyMemoryBoostArtifacts(args.query, args.candidates, artifacts);
}
40 changes: 40 additions & 0 deletions src/lib/rag/rag-hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import { normalizeImageBbox } from "@/lib/image-filtering";
import { committedIndexGeneration, isCommittedGenerationMetadata } from "@/lib/reindex-pipeline";
import { metadataText, safeRecord } from "@/lib/rag/rag-answer-text";
import { compactContextText } from "@/lib/rag/rag-source-block";
import type { RetrievalAccessScope } from "@/lib/owner-scope";
import {
applyMemoryBoostArtifacts,
loadMemoryBoostArtifacts,
type MemoryCardCache,
} from "@/lib/rag/rag-candidate-sources";

// Extracted from rag.ts (maturity X3 / #101): per-request hydration of retrieved
// results — document ranking metadata, cached index quality, and page visual
Expand All @@ -27,6 +33,40 @@ export function createDocumentRankingMetadataCache(): DocumentRankingMetadataCac
};
}

/** Overlap independent metadata and memory reads, then merge them deterministically. */
export async function hydrateCandidatesWithMetadataAndMemory(args: {
supabase: ReturnType<typeof createAdminClient>;
query: string;
candidates: SearchResult[];
queryEmbedding?: number[];
ownerId?: string;
accessScope?: RetrievalAccessScope;
documentIds?: string[];
matchCount: number;
metadataCache: DocumentRankingMetadataCache;
cardCache: MemoryCardCache;
measurePhase: <T>(phase: string, operation: () => Promise<T>) => Promise<T>;
}) {
// Neither read consumes the other's result. Candidate assembly remains ordered because
// memory boosts are applied only after both promises settle, preserving the serial output.
const [metadataCandidates, memoryArtifacts] = await args.measurePhase("metadata_and_memory_hydration", () =>
Promise.all([
attachDocumentRankingMetadata(args.supabase, args.candidates, args.ownerId, args.metadataCache),
loadMemoryBoostArtifacts({
supabase: args.supabase,
query: args.query,
queryEmbedding: args.queryEmbedding,
ownerId: args.ownerId,
accessScope: args.accessScope,
documentIds: args.documentIds,
matchCount: args.matchCount,
cardCache: args.cardCache,
}),
]),
);
return { ...applyMemoryBoostArtifacts(args.query, metadataCandidates, memoryArtifacts), metadataCandidates };
}

/** Attach document ranking metadata. */
export async function attachDocumentRankingMetadata(
supabase: ReturnType<typeof createAdminClient>,
Expand Down
99 changes: 42 additions & 57 deletions src/lib/rag/rag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ import {
attachDocumentRankingMetadata,
attachPageVisualEvidence,
createDocumentRankingMetadataCache,
hydrateCandidatesWithMetadataAndMemory,
type DocumentRankingMetadataCache,
} from "@/lib/rag/rag-hydration";
export { attachDocumentRankingMetadata, attachPageVisualEvidence } from "@/lib/rag/rag-hydration";
Expand Down Expand Up @@ -2106,27 +2107,20 @@ export async function searchChunksWithTelemetry(

if (documentLookupData.length > 0) {
const rerankStartedAt = Date.now();
const documentLookupCandidates = await measureSearchPhase(searchTiming, "metadata_hydration", () =>
attachDocumentRankingMetadata(
supabase,
mergeSearchResults(documentLookupData, textFastResults),
args.ownerId,
documentRankingMetadataCache,
),
);
const memoryBoost = await hydrateCandidatesWithMetadataAndMemory({
supabase,
query: args.query,
candidates: mergeSearchResults(documentLookupData, textFastResults),
ownerId: args.ownerId,
accessScope: args.accessScope,
documentIds: documentFilterList,
matchCount: candidateCount,
metadataCache: documentRankingMetadataCache,
cardCache: memoryCardCache,
measurePhase: (phase, operation) => measureSearchPhase(searchTiming, phase, operation),
});
const documentLookupCandidates = memoryBoost.metadataCandidates;
expandedQuery = expandClinicalQueryWithCandidateMetadata(args.query, expandedQuery, documentLookupCandidates);
const memoryBoost = await measureSearchPhase(searchTiming, "memory_hydration", () =>
withMemoryBoostedCandidates({
supabase,
query: args.query,
candidates: documentLookupCandidates,
ownerId: args.ownerId,
accessScope: args.accessScope,
documentIds: documentFilterList,
matchCount: candidateCount,
cardCache: memoryCardCache,
}),
);
telemetry.memory_card_count = Math.max(telemetry.memory_card_count ?? 0, memoryBoost.cards.length);
telemetry.memory_top_score = Math.max(
telemetry.memory_top_score ?? 0,
Expand Down Expand Up @@ -2357,22 +2351,19 @@ export async function searchChunksWithTelemetry(
if (!hybridError) {
const rerankStartedAt = Date.now();
const merged = args.forceEmbedding ? vectorCandidates : mergeSearchResults(vectorCandidates, textFastResults);
const mergedWithMetadata = await measureSearchPhase(searchTiming, "metadata_hydration", () =>
attachDocumentRankingMetadata(supabase, merged, args.ownerId, documentRankingMetadataCache),
);
const memoryBoost = await measureSearchPhase(searchTiming, "memory_hydration", () =>
withMemoryBoostedCandidates({
supabase,
query: retrievalQuery,
candidates: mergedWithMetadata,
queryEmbedding: embedding,
ownerId: args.ownerId,
accessScope: args.accessScope,
documentIds: documentFilterList,
matchCount: candidateCount,
cardCache: memoryCardCache,
}),
);
const memoryBoost = await hydrateCandidatesWithMetadataAndMemory({
supabase,
query: retrievalQuery,
candidates: merged,
queryEmbedding: embedding,
ownerId: args.ownerId,
accessScope: args.accessScope,
documentIds: documentFilterList,
matchCount: candidateCount,
metadataCache: documentRankingMetadataCache,
cardCache: memoryCardCache,
measurePhase: (phase, operation) => measureSearchPhase(searchTiming, phase, operation),
});
telemetry.memory_card_count = Math.max(telemetry.memory_card_count ?? 0, memoryBoost.cards.length);
telemetry.memory_top_score = Math.max(
telemetry.memory_top_score ?? 0,
Expand Down Expand Up @@ -2445,27 +2436,21 @@ export async function searchChunksWithTelemetry(
mergeSearchResults(resultSets.flat(), embeddingFieldCandidates),
indexUnitCandidates,
);
const mergedWithMetadata = await measureSearchPhase(searchTiming, "metadata_hydration", () =>
attachDocumentRankingMetadata(
supabase,
args.forceEmbedding ? fallbackVectorCandidates : mergeSearchResults(fallbackVectorCandidates, textFastResults),
args.ownerId,
documentRankingMetadataCache,
),
);
const memoryBoost = await measureSearchPhase(searchTiming, "memory_hydration", () =>
withMemoryBoostedCandidates({
supabase,
query: retrievalQuery,
candidates: mergedWithMetadata,
queryEmbedding: embedding,
ownerId: args.ownerId,
accessScope: args.accessScope,
documentIds: documentFilterList,
matchCount: candidateCount,
cardCache: memoryCardCache,
}),
);
const memoryBoost = await hydrateCandidatesWithMetadataAndMemory({
supabase,
query: retrievalQuery,
candidates: args.forceEmbedding
? fallbackVectorCandidates
: mergeSearchResults(fallbackVectorCandidates, textFastResults),
queryEmbedding: embedding,
ownerId: args.ownerId,
accessScope: args.accessScope,
documentIds: documentFilterList,
matchCount: candidateCount,
metadataCache: documentRankingMetadataCache,
cardCache: memoryCardCache,
measurePhase: (phase, operation) => measureSearchPhase(searchTiming, phase, operation),
});
telemetry.memory_card_count = Math.max(telemetry.memory_card_count ?? 0, memoryBoost.cards.length);
telemetry.memory_top_score = Math.max(
telemetry.memory_top_score ?? 0,
Expand Down
7 changes: 5 additions & 2 deletions tests/eval-retrieval.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,16 @@ describe("golden retrieval eval helpers", () => {
telemetry: {
query_class: "table_threshold",
search_total_latency_ms: 321,
retrieval_phase_latencies_ms: { query_classification: 12, metadata_hydration: 34 },
retrieval_phase_latencies_ms: { query_classification: 12, metadata_and_memory_hydration: 34 },
},
latencyMs: 321,
});

expect(evaluated.searchTotalLatencyMs).toBe(321);
expect(evaluated.retrievalPhaseLatenciesMs).toEqual({ query_classification: 12, metadata_hydration: 34 });
expect(evaluated.retrievalPhaseLatenciesMs).toEqual({
query_classification: 12,
metadata_and_memory_hydration: 34,
});
});

it("scores ideal graded signal ranking, coverage, and irrelevant sources", () => {
Expand Down
25 changes: 25 additions & 0 deletions tests/rag-retrieval-parallelism.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { readFileSync } from "node:fs";

import { describe, expect, it } from "vitest";

describe("retrieval hydration parallelism", () => {
it("overlaps only independent metadata and memory reads before deterministic assembly", () => {
const source = readFileSync("src/lib/rag/rag-hydration.ts", "utf8");
const helper = source.slice(
source.indexOf("export async function hydrateCandidatesWithMetadataAndMemory"),
source.indexOf("/** Attach document ranking metadata. */"),
);

expect(helper).toContain("Promise.all([");
expect(helper).toContain("attachDocumentRankingMetadata(");
expect(helper).toContain("loadMemoryBoostArtifacts({");
expect(helper.indexOf("applyMemoryBoostArtifacts(")).toBeGreaterThan(helper.indexOf("Promise.all(["));
});

it("uses the parallel helper on all post-gate vector and document-lookup branches", () => {
const source = readFileSync("src/lib/rag/rag.ts", "utf8");
const callCount = source.match(/await hydrateCandidatesWithMetadataAndMemory\(\{/g)?.length ?? 0;

expect(callCount).toBe(3);
});
});
Comment on lines +1 to +25
Loading