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
6 changes: 0 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ INDEXING_V3_AGENT_SECRET=your-long-random-cron-shared-secret
# Secret required for /api/health?deep=1 Supabase probe (x-health-deep-token header).
HEALTH_DEEP_PROBE_SECRET=your-long-random-health-deep-probe-secret

# Optional server-side Sentry error capture (answer pipeline + uncaught route errors).
# Fully inert when unset: the SDK is never imported and no events leave the server.
# Events are scrubbed at init (no request bodies/headers/breadcrumbs) — see
# src/instrumentation.ts and src/lib/observability/error-capture.ts.
#SENTRY_DSN=

# Local-only no-auth mode (development only).
# Enable one or both of these to load real data without per-request browser auth:
# - NEXT_PUBLIC_LOCAL_NO_AUTH (client + server)
Expand Down
1 change: 0 additions & 1 deletion docs/site-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,6 @@ This file is generated by `npm run sitemap:update`. Run `npm run sitemap:check`
- `/api/local-project-id` - Local project identity guard. Source: `src/app/api/local-project-id/route.ts`.
- `/api/medications` - Route discovered from app directory Source: `src/app/api/medications/route.ts`.
- `/api/medications/[slug]` - Route discovered from app directory Source: `src/app/api/medications/[slug]/route.ts`.
- `/api/monitoring` - Route discovered from app directory Source: `src/app/api/monitoring/route.ts`.
- `/api/registry/records` - Registry record collection. Source: `src/app/api/registry/records/route.ts`.
- `/api/registry/records/[slug]` - Registry record detail. Source: `src/app/api/registry/records/[slug]/route.ts`.
- `/api/search` - Search endpoint. Source: `src/app/api/search/route.ts`.
Expand Down
14 changes: 1 addition & 13 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,12 @@ const nextConfig: NextConfig = {
turbopack: {
root: projectRoot,
},
webpack(config, { webpack }) {
webpack(config) {
// Avoid a Next/webpack WasmHash worker crash observed on Node 24 during local production builds.
config.output = {
...config.output,
hashFunction: "sha256",
};
// Build-time flag so the browser Sentry SDK (@sentry/browser) is fully
// tree-shaken out unless a public DSN is set at build. Next does NOT fold an
// UNSET NEXT_PUBLIC_* var to a compile-time constant, so a plain
// `if (process.env.NEXT_PUBLIC_SENTRY_DSN)` gate leaves the dynamic import (and
// its chunk) on disk. This literal boolean lets webpack dead-code-eliminate the
// whole block, so an unconfigured build ships zero Sentry bytes. See
// src/instrumentation-client.ts.
config.plugins.push(
new webpack.DefinePlugin({
__SENTRY_ENABLED__: JSON.stringify(Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN)),
}),
);
return config;
},
async headers() {
Expand Down
185 changes: 0 additions & 185 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,6 @@
},
"dependencies": {
"@next/env": "16.2.10",
"@sentry/browser": "^10.65.0",
"@sentry/node": "^10.65.0",
"@supabase/ssr": "^0.12.0",
"@supabase/supabase-js": "^2.108.2",
"exceljs": "^4.4.0",
Expand Down
13 changes: 0 additions & 13 deletions src/app/api/answer/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
} from "@/lib/answer-response";
import { answerServerTimingEntries, buildServerTimingHeader } from "@/lib/server-timing";
import { createAdminClient } from "@/lib/supabase/admin";
import { captureServerException } from "@/lib/observability/error-capture";
import { logAnswerDiagnostics } from "@/lib/answer-telemetry";
import { nonProductionSupabaseDemoFallbackReason } from "@/lib/supabase/errors";
import * as serverAuth from "@/lib/supabase/auth";
Expand Down Expand Up @@ -146,13 +145,7 @@ export async function POST(request: Request) {
if (error instanceof z.ZodError) {
return jsonError(error, 400);
}
const clientAborted = (error instanceof DOMException && error.name === "AbortError") || request.signal.aborted;
if (error instanceof PublicApiError) {
// Expected degradations (rate limits, provider quota/timeouts mapped < 500)
// are operational noise; only server-fault statuses are reported.
if (error.status >= 500 && !clientAborted) {
void captureServerException(error, { route: "api/answer", status: error.status });
}
return jsonError(error, error.status);
}
if (error instanceof Error) {
Expand All @@ -164,17 +157,11 @@ export async function POST(request: Request) {
{ headers: { "X-Clinical-KB-Fallback": fallbackReason } },
);
}
if (!clientAborted) {
void captureServerException(error, { route: "api/answer", status: 500 });
}
return jsonError(
new PublicApiError("Answer generation failed. Retry with a narrower question.", 500, { code: error.name }),
500,
);
}
if (!clientAborted) {
void captureServerException(error, { route: "api/answer", status: 500 });
}
return jsonError("Answer generation failed.", 500);
}
}
19 changes: 4 additions & 15 deletions src/app/api/answer/stream/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { resolveSearchScope } from "@/lib/search-scope";
import { resolveRetrievalAccessScope, type RetrievalAccessScope } from "@/lib/owner-scope";
import { sourceGovernanceWarnings } from "@/lib/source-governance";
import { createAdminClient } from "@/lib/supabase/admin";
import { captureServerException } from "@/lib/observability/error-capture";
import { logAnswerDiagnostics } from "@/lib/answer-telemetry";
import { isSupabaseApiKeyConfigurationError, nonProductionSupabaseDemoFallbackReason } from "@/lib/supabase/errors";
import { AuthenticationError, unauthorizedResponse } from "@/lib/supabase/auth";
Expand Down Expand Up @@ -94,12 +93,12 @@ function streamAnswerFeedbackMetadata(interactionId: string, answer: string) {
}

function logStreamError(error: unknown, signal?: AbortSignal) {
logger.error("Search stream failed", safeErrorLogDetails(error));
// Report only server-fault failures: client aborts (Stop button / watchdog) and
// expected sub-500 degradations are operational noise, not incidents.
// Client aborts (Stop button / watchdog) and expected sub-500 degradations are
// operational noise, not failures — the caller still surfaces them to the client
// via the SSE error event. Only genuine server-fault stream failures are logged.
if ((error instanceof DOMException && error.name === "AbortError") || signal?.aborted) return;
if (error instanceof PublicApiError && error.status < 500) return;
void captureServerException(error, { route: "api/answer/stream", source: "stream" });
logger.error("Search stream failed", safeErrorLogDetails(error));
}

function buildDemoStreamAnswer(body: AnswerRequestBody, fallbackReason?: string) {
Expand Down Expand Up @@ -301,22 +300,12 @@ export async function POST(request: Request) {
if (error instanceof z.ZodError) {
return jsonError(error, 400);
}
const clientAborted = (error instanceof DOMException && error.name === "AbortError") || request.signal.aborted;
if (error instanceof PublicApiError) {
if (error.status >= 500 && !clientAborted) {
void captureServerException(error, { route: "api/answer/stream", status: error.status });
}
return jsonError(error, error.status);
}
if (error instanceof Error) {
if (!clientAborted) {
void captureServerException(error, { route: "api/answer/stream", status: 500 });
}
return jsonError(new PublicApiError("Answer processing failed.", 500, { code: error.name }), 500);
}
if (!clientAborted) {
void captureServerException(error, { route: "api/answer/stream", status: 500 });
}
return jsonError("Answer processing failed.", 500);
}
}
Loading