Skip to content
Closed
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
7 changes: 1 addition & 6 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ import { HomePageClient } from "@/app/home-page-client";
import { appModeHomeHref, isAppModeId, isAppModeVisible, type AppModeId } from "@/lib/app-modes";

type HomeProps = {
searchParams?: Promise<{
mode?: string | string[];
q?: string | string[];
focus?: string | string[];
run?: string | string[];
}>;
searchParams?: Promise<Record<string, string | string[] | undefined>>;
};

function firstSearchParam(value: string | string[] | undefined) {
Expand Down
31 changes: 12 additions & 19 deletions src/components/forms/forms-search-results-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ type FormsSearchResultsPageProps = {
query: string;
};

const sourceSnippetCount = 278;
const taskCount = 8;
const pathwayCount = 12;
const supportsPathwayClaims = false;

const refineFilters: {
icon: LucideIcon;
Expand Down Expand Up @@ -94,9 +92,6 @@ function ResultTabs({ formsCount }: { formsCount: number }) {
const tabs = [
["Results", null],
["Forms", formsCount],
["Evidence", sourceSnippetCount],
["Pathways", pathwayCount],
["Tasks", taskCount],
] as const;

return (
Expand Down Expand Up @@ -669,14 +664,16 @@ function FormsSearchResultsPageContent({ query }: FormsSearchResultsPageProps) {
</div>
<div className="flex items-center gap-2 pb-1.5">
<ResultSortControl value={sortValue} onChange={setSortValue} className="md:hidden" />
<RefineBar
open={refineOpen}
onToggle={() => setRefineOpen((open) => !open)}
panelId={refinePanelId}
/>
{supportsPathwayClaims ? (
<RefineBar
open={refineOpen}
onToggle={() => setRefineOpen((open) => !open)}
panelId={refinePanelId}
/>
) : null}
</div>
</div>
<RefinePanel open={refineOpen} panelId={refinePanelId} />
{supportsPathwayClaims ? <RefinePanel open={refineOpen} panelId={refinePanelId} /> : null}
<div className="hidden md:block">
<ResultsTable matches={displayedMatches} query={query} sortValue={sortValue} />
</div>
Expand All @@ -688,13 +685,9 @@ function FormsSearchResultsPageContent({ query }: FormsSearchResultsPageProps) {
<UniversalSearchAlsoMatches modeId="forms" query={query} />
</>
) : null}
<div className="hidden lg:block">
<PathwayPanel />
</div>
<div className="lg:hidden">
<MobilePathway />
</div>
{registryReady ? <VerifiedFooter /> : null}
<div className="hidden lg:block">{supportsPathwayClaims ? <PathwayPanel /> : null}</div>
<div className="lg:hidden">{supportsPathwayClaims ? <MobilePathway /> : null}</div>
{registryReady && supportsPathwayClaims ? <VerifiedFooter /> : null}
</main>
</div>
);
Expand Down
22 changes: 17 additions & 5 deletions src/lib/rag-candidate-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ import type { DocumentIndexUnitMatch, DocumentMemoryCard, SearchResult } from "@
// the floor, which is how the live schema drift (42702) went unnoticed. Log it structurally and,
// where telemetry is in scope, record the failing RPC + code so it shows up in rag_retrieval_logs.
export type SupabaseRpcError = { message?: string; code?: string; details?: string; hint?: string } | null;
type RpcResult<T> = Promise<{ data: T | null; error: SupabaseRpcError }>;
type AbortableRpc<T> = RpcResult<T> & {
abortSignal?: (signal: AbortSignal) => RpcResult<T>;
};
type SupabaseRpcClient = {
rpc: (name: string, rpcArgs: Record<string, unknown>) => AbortableRpc<unknown[]> | PromiseLike<unknown>;
};

function legacyRankFields(versionedName: string) {
if (versionedName === "match_document_chunks_v2") return ["similarity"];
Expand Down Expand Up @@ -81,15 +88,20 @@ export async function callVersionedRetrievalRpc<T extends unknown[] = unknown[]>
versionedName: string,
legacyName: string,
args: Record<string, unknown>,
signal?: AbortSignal,
): Promise<{ data: T | null; error: SupabaseRpcError }> {
const client = supabase as unknown as {
rpc: (name: string, rpcArgs: Record<string, unknown>) => Promise<{ data: T | null; error: SupabaseRpcError }>;
const client = supabase as unknown as SupabaseRpcClient;
const executeRpc = async (name: string, rpcArgs: Record<string, unknown>) => {
const pending = client.rpc(name, rpcArgs) as AbortableRpc<T>;
const pendingWithAbort =
signal && typeof pending.abortSignal === "function" ? pending.abortSignal(signal) : pending;
return await pendingWithAbort;
};
const versioned = await client.rpc(versionedName, args);
const versioned = await executeRpc(versionedName, args);
if (versioned && !isMissingRetrievalRpcError(versioned.error)) return versioned;
const legacyArgs = { ...args };
delete legacyArgs.include_public;
const ownerResult = await client.rpc(legacyName, legacyArgs);
const ownerResult = await executeRpc(legacyName, legacyArgs);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const ownerFilter = String(args.owner_filter ?? "");
if (
ownerResult.error ||
Expand All @@ -99,7 +111,7 @@ export async function callVersionedRetrievalRpc<T extends unknown[] = unknown[]>
) {
return ownerResult;
}
const publicResult = await client.rpc(legacyName, {
const publicResult = await executeRpc(legacyName, {
...legacyArgs,
owner_filter: PUBLIC_OWNER_FILTER_SENTINEL,
});
Expand Down
60 changes: 56 additions & 4 deletions src/lib/rag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createAdminClient } from "@/lib/supabase/admin";
import { retrievalAccessScopeForArgs, retrievalRpcScopeArgs } from "@/lib/owner-scope";
import {
callVersionedRetrievalRpc,
createChunkLoadCache,
memoryCardChunkScore,
mergeSearchResults,
recordHybridRpcError,
Expand All @@ -11,7 +12,6 @@ import {
searchTableFactCandidates,
searchTextChunkCandidates,
withMemoryBoostedCandidates,
createChunkLoadCache,
type MemoryCardCache,
} from "@/lib/rag-candidate-sources";
export {
Expand Down Expand Up @@ -120,6 +120,11 @@ export {
retrievalPlanCacheQuery,
} from "@/lib/rag-cache";
import { classifySearchCacheOutcome, recordCacheLookup } from "@/lib/observability/cache-metrics";
import {
recordAnswerOrigination,
recordAnswerOriginationFinished,
recordCoalescedAnswerWaiter,
} from "@/lib/observability/answer-coalescing-metrics";
import { buildRagSourceBlock, compactContextText, neutralizeIdentityField } from "@/lib/rag-source-block";
export { buildRagSourceBlock, truncateForModel } from "@/lib/rag-source-block";
import {
Expand Down Expand Up @@ -423,6 +428,26 @@ function throwIfAborted(signal?: AbortSignal) {
}
}

function awaitWithCallerSignal<T>(pending: Promise<T>, signal?: AbortSignal): Promise<T> {
if (!signal) return pending;
if (signal.aborted) throw signal.reason ?? new DOMException("The operation was aborted.", "AbortError");

return new Promise<T>((resolve, reject) => {
const onAbort = () => reject(signal.reason ?? new DOMException("The operation was aborted.", "AbortError"));
signal.addEventListener("abort", onAbort, { once: true });
pending.then(
(value) => {
signal.removeEventListener("abort", onAbort);
resolve(value);
},
(error) => {
signal.removeEventListener("abort", onAbort);
reject(error);
},
);
});
}

export type AnswerProgressEvent = {
stage:
| "retrieved"
Expand Down Expand Up @@ -1290,6 +1315,7 @@ export async function analyzeQueryWithClassifierFallback(
// owner_filter retrieval will use so grounding can never see documents retrieval cannot.
corpusGrounding?: { supabase: ReturnType<typeof createAdminClient>; ownerFilter: string | null };
ownerId?: string | null;
signal?: AbortSignal;
},
) {
if (
Expand Down Expand Up @@ -1361,10 +1387,17 @@ export async function analyzeQueryWithClassifierFallback(
}

try {
const verdict = await pending;
const verdict = await awaitWithCallerSignal(pending, opts?.signal);
storeClassifierVerdictMemo(memoKey, verdict);
return applyClassifierVerdict(analysis, verdict);
} catch {
} catch (error) {
if (opts?.signal?.aborted) throw opts.signal.reason ?? error;
if (
error &&
(error instanceof DOMException || typeof error === "object") &&
(error as { name?: string }).name === "AbortError"
)
throw error;
// Transport/parse failures are deliberately NOT memoized: fall back to the deterministic
// analysis for this request only, and let the next request retry the classifier.
return analysis;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down Expand Up @@ -2398,6 +2431,7 @@ export async function searchChunksWithTelemetry(args: SearchChunksArgs) {
const queryAnalysis = await analyzeQueryWithClassifierFallback(retrievalQuery, analyzeClinicalQuery(retrievalQuery), {
corpusGrounding: corpusGroundingScope,
ownerId: args.ownerId,
signal: args.signal,
});
throwIfAborted(args.signal);
if (modeQueryClass) queryAnalysis.queryClass = modeQueryClass;
Expand Down Expand Up @@ -2813,6 +2847,7 @@ export async function searchChunksWithTelemetry(args: SearchChunksArgs) {
document_filters: documentFilterList ?? undefined,
...retrievalRpcScopeArgs(retrievalAccessScopeForArgs(args)),
},
args.signal,
);
return { data, error, latencyMs: Date.now() - startedAt };
})(),
Expand Down Expand Up @@ -2920,12 +2955,21 @@ export async function searchChunksWithTelemetry(args: SearchChunksArgs) {
document_filter: documentFilter ?? undefined,
...retrievalRpcScopeArgs(retrievalAccessScopeForArgs(args)),
},
args.signal,
Comment thread
cursor[bot] marked this conversation as resolved.
);

if (error) throw new Error(error.message);
return (data ?? []) as SearchResult[];
}),
).catch((error) => {
if (args.signal?.aborted) throw args.signal.reason ?? error;
if (
error &&
(error instanceof DOMException || typeof error === "object") &&
(error as { name?: string }).name === "AbortError"
) {
throw error;
}
if (!args.forceEmbedding && textFastResults.length > 0) return [] as SearchResult[][];
throw error;
});
Expand Down Expand Up @@ -3123,6 +3167,7 @@ export async function answerQuestionWithScope(args: AnswerQuestionWithScopeArgs)
let existing = inflightKey ? answerInflight.get(inflightKey) : undefined;

while (existing) {
recordCoalescedAnswerWaiter();
await args.onProgress?.({
stage: "cached",
message: "Waiting for an identical cited answer request already in progress.",
Expand Down Expand Up @@ -3154,8 +3199,15 @@ export async function answerQuestionWithScope(args: AnswerQuestionWithScopeArgs)
}
}

// Only coalescible requests belong in this process-local signal. Requests
// that intentionally bypass cache/coalescing must not make a replica look
// ineffective, and neither keys nor clinical content leave this function.
if (inflightKey) recordAnswerOrigination();
const pending = answerQuestionWithScopeUncoalesced(args, startedAt).finally(() => {
if (inflightKey) answerInflight.delete(inflightKey);
if (inflightKey) {
answerInflight.delete(inflightKey);
recordAnswerOriginationFinished();
}
});
if (inflightKey) answerInflight.set(inflightKey, pending);
return pending;
Expand Down
43 changes: 13 additions & 30 deletions tests/audit-content-services-regressions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,15 @@ describe("content and services audit regressions", () => {
},
});
expect(transport?.source).toHaveProperty("url");
expect(transport?.source).not.toHaveProperty("published");
expect(transport?.source).toHaveProperty("reviewed");
expect(transport?.source).not.toHaveProperty("pages");
expect(transport?.source).not.toHaveProperty("pageCount");
expect(transport?.source).not.toHaveProperty("reviewDue");
expect(JSON.stringify(transport?.source)).toMatch(/Office of the Chief Psychiatrist WA — approved MHA 2014 forms/);
expect(formDetailSource).not.toMatch(/Review due/i);
expect(formDetailSource).not.toMatch(/Admission order|Treatment order|5\(2\)/);
expect(formDetailSource).toContain("Pathway navigation is not available yet");
expect(JSON.stringify(transport?.source)).not.toMatch(/\b\d+\s+pages?\b|\bstatutory\b/i);
expect(formDetailSource).not.toMatch(/\b\d+\s+pages?\b|\bReview due\b/i);
expect(formDetailSource).not.toContain("01 May 2026");
expect(formDetailSource).not.toMatch(/5\(2\)|Admission order|Treatment order/);
expect(formDetailSource).toContain("Full pathway unavailable");
expect(formDetailSource).toContain(">Source info</");
expect(normalizedFormDetailSource).toContain(
'label: "Source currency", value: displayText(form.source?.reviewed, "Review locally")',
);
Expand All @@ -134,33 +132,18 @@ describe("content and services audit regressions", () => {
if (form.source?.url) continue;
expect(form.verification?.locallyVerified, form.slug).toBe(false);
expect(form.verification?.confidence, form.slug).toBe("Unknown");
expect(form.source?.status, form.slug).toMatch(/confirmation required|source checked/i);
expect(form.source?.status, form.slug).toMatch(/confirmation required/i);
expect(form.source?.reviewed, form.slug).toBeUndefined();
}

expect(formsSearchSource).toContain("const sourceSnippetCount = 278;");
expect(formsSearchSource).toContain("const taskCount = 8;");
expect(formsSearchSource).toContain("const pathwayCount = 12;");
expect(formsSearchSource).toContain('["Evidence", sourceSnippetCount]');
expect(formsSearchSource).toContain('["Pathways", pathwayCount]');
expect(formsSearchSource).toContain('["Tasks", taskCount]');
expect(formsSearchSource).toContain("PSOLIS");
expect(formsSearchSource).toContain("Source verified");
expect(formsSearchSource).toContain("Official source");
expect(formsSearchSource).toContain("Aligned to MHA 2014");
expect(formsSearchSource).toContain("Open account setup");
expect(formsSearchSource).toContain("View full pathway");
expect(formsSearchSource).toContain("Filter controls are coming soon");
expect(formsSearchSource).toContain("tagToneClass(chipLabel)");
expect(formsSearchSource).toContain("Title or content match");
expect(formsSearchSource).toContain("Content match in related pathway");
expect(formsSearchSource).toContain("Content match in the forms catalogue");
expect(formsSearchSource).toContain("View all forms (");
expect(formsSearchSource).toContain("View all forms");
expect(formsHomeSource).not.toMatch(/Source verified|Open account setup/);
expect(formsHomeSource).not.toMatch(
/Number, pathway, clock|Maker, clock, copies|Browse pathways|Before, current, parallel, after|starter set of MHA 2014 forms|follow a pathway/,
);
expect(formsHomeSource).toContain("need local confirmation");
expect(formsHomeSource).toContain("local confirmation");
expect(formsHomeSource).toContain("Source catalogue reviewed");
});

Expand Down Expand Up @@ -215,12 +198,12 @@ describe("content and services audit regressions", () => {
});

it("claims and renders a form source link only when the record has a URL", () => {
expect(normalizedFormDetailSource).toContain("form.source?.url ? (");
expect(normalizedFormDetailSource).toMatch(/\{form\.source\?\.url \? \( <a href=\{form\.source\.url\}/);
expect(normalizedFormDetailSource).toMatch(
/<a href=\{form\.source\.url\} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-10/,
);
expect(formDetailSource.match(/Source link pending/g)).toHaveLength(1);
expect(normalizedFormDetailSource).toContain("sourceHref={form.source?.url ?? null}");
expect(normalizedFormDetailSource).toContain("href={form.source.url}");
expect(normalizedFormDetailSource).toContain('target="_blank"');
expect(normalizedFormDetailSource).toContain('rel="noopener noreferrer"');
expect(normalizedFormDetailSource).toContain("inline-flex min-h-10");
expect(formDetailSource).toContain("Source link pending");
expect(formDetailSource).toContain("Official");
});
});
11 changes: 5 additions & 6 deletions tests/audit-navigation-auth-regressions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,17 @@ describe("audit navigation and auth regressions", () => {
);
expect(privateCapabilityContract).toContain("const canUsePrivateApis =");
expect(privateCapabilityContract).toContain(
'localProjectReady && (localNoAuthMode || localDevCanAttemptPrivateApis || authStatus === "authenticated")',
'localNoAuthMode || localDevCanAttemptPrivateApis || authStatus === "authenticated"',
);
expect(privateCapabilityContract).not.toMatch(/clientDemoMode/);

const pollingContract = sourceSegment(
clinicalDashboardSource,
"if (!nextDemoMode && !canUsePrivateApis) {",
"const shouldRefreshWorkState =",
"const [documentsResponse",
);
expect(pollingContract).toContain(
"includeAdministrationData && (!administrationDataLoadedRef.current || now >= nextWorkStatePollRef.current)",
);
expect(pollingContract).toContain("if (!nextDemoMode && !canUsePrivateApis) {");
expect(pollingContract).toContain("setDocuments([]);");
expect(pollingContract).toContain("return;");

const labelMutationContract = sourceSegment(
clinicalDashboardSource,
Expand Down
Loading