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
17 changes: 15 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ OPENAI_STRONG_ANSWER_MODEL=gpt-5.6-sol
OPENAI_QUERY_CLASSIFIER_MODEL=gpt-5.6-luna
OPENAI_SUMMARY_MODEL=gpt-5.6-terra
OPENAI_INDEXING_MODEL=gpt-5.6-terra
OPENAI_MAX_OUTPUT_TOKENS=4000
# Answer-generation output ceiling (billed per token actually used, not a spend
# commitment). Must stay well above reasoning headroom: the old 4000 starved the
# strong route's reasoning budget and discarded the answer after 60-90s (GEN-C1).
# Matches the env.ts default.
OPENAI_MAX_OUTPUT_TOKENS=16000
OPENAI_QUERY_CACHE_SIZE=200
# Max inputs per embeddings request (OpenAI caps a request at 2048 inputs / ~300k tokens).
# embedTexts splits a full-corpus re-embed into batches of this size.
Expand All @@ -76,7 +80,10 @@ OPENAI_PROMPT_CACHE_TTL=30m
#OPENAI_SAFETY_IDENTIFIER_SECRET=
OPENAI_STORE_RESPONSES=false
OPENAI_FAST_REASONING_EFFORT=low
OPENAI_STRONG_REASONING_EFFORT=high
# "high" overruns OPENAI_ANSWER_TIMEOUT_MS and starves the safety-critical
# medication_dose_risk/table_threshold classes; "medium" is ample for answers
# grounded in retrieved sources and matches the env.ts default.
OPENAI_STRONG_REASONING_EFFORT=medium
OPENAI_SUMMARY_REASONING_EFFORT=medium
OPENAI_VISION_REASONING_EFFORT=low
OPENAI_TEXT_VERBOSITY=low
Expand Down Expand Up @@ -104,6 +111,12 @@ RAG_AWAIT_QUERY_LOGS=false
# Design-exploration mockup routes (/mockups/*) 404 in production builds unless
# explicitly opted in. Always reachable in dev/test.
#NEXT_PUBLIC_MOCKUPS_ENABLED=false
# Allow unauthenticated visitors to upload documents — gates an anonymous write
# path, so leave unset (off) unless you intend a public-intake workspace. When
# enabled, anonymous uploads are owned by PUBLIC_WORKSPACE_OWNER_ID so that
# owner-scoped reads still apply to them.
#NEXT_PUBLIC_PUBLIC_UPLOADS_ENABLED=false
#PUBLIC_WORKSPACE_OWNER_ID=
# Optional canonical public origin for generated metadata, for example
# https://clinical-kb.example.org. Railway deployments otherwise use the
# provider-supplied RAILWAY_PUBLIC_DOMAIN; request hosts are only used in dev.
Expand Down
4 changes: 4 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export default defineConfig({
// hydration, sub-pixel tap targets) without masking a real regression, which
// still fails 3× before it is reported. Local stays at 0 so flakes surface.
retries: process.env.CI ? 2 : 0,
// Fail the run if a stray `test.only` is committed: otherwise it silently
// narrows CI to that one test (and skips the whole release matrix) while the
// required check still reports green.
forbidOnly: !!process.env.CI,
expect: {
timeout: 10_000,
},
Expand Down
7 changes: 5 additions & 2 deletions src/app/api/documents/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,14 @@ export async function GET(request: Request) {

const { data, error, count } = await query;

if (error) throw new Error(error.message);
// An `offset` past the end of the result set makes PostgREST return PGRST103
// ("Requested range not satisfiable"). That is an empty page, not a server
// error, so return an empty page instead of throwing a 500.
if (error && error.code !== "PGRST103") throw new Error(error.message);
// An authenticated caller reads PUBLIC (owner_id IS NULL) documents alongside their own via
// withOwnerReadScope. Redact operator-internal storage fields on the rows they do not own so a
// shared public document never exposes its owner's storage_path/content_hash/etc. (S1/D1).
const rawDocuments = (data ?? []) as unknown as DocumentListRow[];
const rawDocuments = (error ? [] : (data ?? [])) as unknown as DocumentListRow[];
const ownedDocumentIds = new Set(
rawDocuments.filter((document) => callerOwnsDocumentRow(document, access.ownerId)).map((document) => document.id),
);
Expand Down
3 changes: 2 additions & 1 deletion src/lib/rag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ import {
queryTextForStorage,
} from "@/lib/query-privacy";
import { normalizeSourceMetadata } from "@/lib/source-metadata";
import { safeErrorLogDetails } from "@/lib/privacy";
import { isClinicalImageEvidence, normalizeImageBbox } from "@/lib/image-filtering";
import {
SOURCE_BACKED_REVIEW_FALLBACK_REASON,
Expand Down Expand Up @@ -3042,7 +3043,7 @@ export function parseAnswerJson(raw: string, results: SearchResult[], query?: st
// GEN-C2 / GEN-H2: numeric faithfulness gate.
return enrichGroundedReviewCitations(applyNumericVerification(answer), results);
} catch (error) {
console.warn("Failed to parse answer payload, falling back to safe text:", error);
console.warn("Failed to parse answer payload, falling back to safe text:", safeErrorLogDetails(error));
return safeFallbackAnswer(raw, results, query);
}
}
Expand Down