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
13 changes: 13 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ 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 Sentry error + tracing capture. Fully inert when unset (no SDK import,
# no egress). Events are scrubbed at init (no request bodies/headers/breadcrumbs/
# user) — see sentry.server.config.ts and src/lib/observability/error-capture.ts.
# Session Replay is intentionally disabled (clinical DOM would leak query text).
#SENTRY_DSN=
#SENTRY_ENVIRONMENT=production
#NEXT_PUBLIC_SENTRY_DSN=
#NEXT_PUBLIC_SENTRY_ENVIRONMENT=production
# Build-time source-map upload (optional; distinct from the DSN).
#SENTRY_ORG=
#SENTRY_PROJECT=
#SENTRY_AUTH_TOKEN=

# --- Inbound webhook receivers (see docs/webhooks.md) ---
# Gate for POST /api/webhooks/railway. Railway can only set a target URL, so the
# secret travels as ?token=... on the configured webhook URL. Min 16 chars.
Expand Down
12 changes: 6 additions & 6 deletions docs/codebase-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,12 @@ domain-extracted directory; imported as `@/lib/rag/rag*`). Other modules below r

### Infra helpers

| Module | Role |
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------- |
| `openai.ts`, `embedding-dimensions.ts`, `api-rate-limit.ts` | External APIs and rate limits |
| `observability/` — `answer-slo.ts`, `cache-metrics.ts`, `spend-metrics.ts` | Deep-health SLO / cache-hit / answer-spend snapshots |
| `validation/` | `body.ts`, `query.ts`, `params.ts`, `http.ts`, `form-data.ts` |
| `app-modes.ts`, `document-flow-routes.ts`, `local-project-identity.ts`, `local-server-utils.mjs` | Routing and project identity |
| Module | Role |
| ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `openai.ts`, `embedding-dimensions.ts`, `api-rate-limit.ts` | External APIs and rate limits |
| `observability/` — `answer-slo.ts`, `cache-metrics.ts`, `spend-metrics.ts`, `error-capture.ts`, `sentry-scrub.ts`, `sentry-client.ts` | Deep-health SLO / cache-hit / answer-spend snapshots; optional privacy-scrubbed Sentry capture (inert without DSN) |
| `validation/` | `body.ts`, `query.ts`, `params.ts`, `http.ts`, `form-data.ts` |
| `app-modes.ts`, `document-flow-routes.ts`, `local-project-identity.ts`, `local-server-utils.mjs` | Routing and project identity |

---

Expand Down
4 changes: 2 additions & 2 deletions docs/outstanding-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ removed after current-main verification; it is not missing recommended work.
| 20 | `#013`, `#016` | A3 | High — bundling/runtime performance | After `#017` or equivalent evidence | 0.5–2 days/route | Optimize only a production route with measured payload/render/motion harm. Require material gain plus focused, `verify:cheap`, and browser evidence; stop on small gain. |
| 21 | `#035` | A3 | Specialist — evidence rules | After a demonstrated missed conflict | 0.5–1 day design; code separate | Define a clinically reviewed conflict class with positive and negative fixtures. Stop if no bounded class can be shown; behavior change requires protected review. |
| 22 | `#027` | Optional | Operator — SRE/provider | When an owned external alert path is wanted | 1–2 hours | Decide vendor/cost/privacy/owner; if accepted, prove one non-PHI outage and recovery alert. Stop when no responder owns it. |
| 23 | `#028` | Optional | Specialist privacy/observability + Operator | After privacy/ownership/cost approval | 1–3 days | Define vendor/region/retention/redaction/sampling/source-map envelope before SDK work. Prove no clinical text, identifiers, or secrets leave; stop if unacceptable. |
| 23 | `#028` | Optional | Specialist privacy/observability + Operator | After DSN + MCP verify | 1–2 hours ops | SDK scaffolding lands in PR #1516 (privacy-scrubbed, inert without DSN). Remaining: authenticate Sentry MCP or supply DSNs, confirm one real error, set Railway secrets + source-map token, then alert routing. Stop if clinical text appears in any event. |
| 24 | `#038` | Optional | High — product/design architecture | When a new comparison surface is approved | 0.5–1 day | Define a shared interaction contract without flattening mode-specific content. Stop when no concrete new surface exists. |
| 25 | `#040` | Optional | High — visual QA/accessibility | When baseline owner/update workflow exists | 1–2 days | Establish a small stable desktop/mobile/accessibility baseline set. Do not make it blocking if flake or maintenance cost outweighs detection value. |
| 26 | `#039` | Optional | High — frontend architecture | During a concrete catalogue-toolbar project | 0.5–1 day inventory; 1–3 days code | Converge only repeated toolbar behavior without flattening search semantics. Stop when there is no bounded implementation target. |
Expand Down Expand Up @@ -116,7 +116,7 @@ removed after current-main verification; it is not missing recommended work.
| #024 | P2 | issue | WebKit e2e `_rsc` prefetch access-control errors | PR #1205 narrowed catch-all interception and duplicate navigation, but Next 16.2.11 still raises `_rsc` access-control `pageerror`s after document-source fallbacks: `/documents/source?id=<demo-id>&page=2&chunk=safety%20plan` → `/documents/<demo-id>?page=2&chunk=safety+plan`; `/documents/source/evidence?id=not-a-uuid&page=2` → `/documents/search`. The invalid-id failure survived removing every Playwright route; Chromium passed both. **Next:** on a provider-free macOS host, run both URLs in stable Safari and Safari Technology Preview without interception, capture console text plus `_rsc` status/access-control headers, and compare Playwright WebKit with routing on/off. Treat as an app defect only if native Safari reproduces; otherwise return to the harness. Never suppress `pageerror` or change CORS without native evidence. | PRs #1179/#1205; current-main local WebKit evidence; session 2026-07-28 | 2026-07-28 |
| #025 | P2 | task | Activate the three webhooks (operator secrets) | Merged (#968/#1100) + deployed but inert — verified live: `POST /api/webhooks/railway` returns `503 webhook_not_configured`; the Supabase document-change trigger exists but lacks both activation inputs. To turn on: (1) Railway ΓåÆ set `RAILWAY_WEBHOOK_SECRET` + add the `?token=…` webhook URL; (2) set `SLACK_WEBHOOK_URL`/`DISCORD_WEBHOOK_URL` in BOTH the Railway **app/server env** and **GitHub repo secrets**; (3) set one matching document-change secret in the Railway app env as `SUPABASE_INGESTION_WEBHOOK_SECRET` and in Supabase Vault as `ingestion_webhook_secret`, then set the per-environment database GUC `app.ingestion_webhook_base_url` to the deployed app origin. Each path fails closed until fully configured, so this is pure ops. See `docs/webhooks.md` for verification and rotation. | sessions 2026-07-22/24; PRs #968/#1100; docs/webhooks.md | 2026-07-22 |
| #027 | P3 | rec | External uptime monitor independent of GitHub/Railway | `live-domain-monitor.yml` runs on GitHub's cron, so it won't run in exactly the outage it should catch (Actions or the deploy itself down). Add an off-platform synthetic monitor (UptimeRobot / Better Stack / Checkly) hitting `/api/health` with a webhook alert. Provider setup, not code. | session 2026-07-22 webhook review | 2026-07-22 |
| #028 | P3 | rec | Runtime error tracking (Sentry or similar) | No error tracking in the repo — production exceptions on `psychiatry.tools`, including how often `RAG_PROVIDER_MODE=auto` silently degrades to source-only, are invisible. Weigh adding `@sentry/nextjs` (dependency + DSN secret + instrumentation) vs cost; alert ΓåÆ chat/issue. Provider-backed; needs explicit sign-off before adding the dependency. | session 2026-07-22 webhook review | 2026-07-22 |
| #028 | P3 | rec | Runtime error tracking (Sentry or similar) | **Partial (PR #1516):** `@sentry/nextjs` is wired with errors+tracing, clinical scrubbing (no request/user/breadcrumbs; no Session Replay), same-origin `/monitoring` tunnel, and answer-path capture for `>=500` faults + `answer_generation_fallback`. Fully inert without DSN. **Next:** connect Sentry MCP (or provide `SENTRY_DSN` / `NEXT_PUBLIC_SENTRY_DSN`), verify one real app error lands with readable frames, set Railway + build `SENTRY_AUTH_TOKEN`/`SENTRY_ORG`/`SENTRY_PROJECT`, then alert → chat/issue. Stop if any clinical query/document text appears in an event. | session 2026-07-22 webhook review; PR #1516 | 2026-07-31 |
| #033 | P3 | rec | Source governance metadata absent from the LLM prompt | `buildRagSourceBlock` omits `document_status`, `clinical_validation_status`, and `extraction_quality`, so the model cannot self-caveat during generation and governance is enforced only post-hoc. Generation-surface change: needs `eval:rag` plus `eval:quality --rag-only` (grounded-supported must not drop, citation-failure 0) and explicit approval. Carries the same "unknown Γëá bad" hazard as #032 — on a partially-enriched corpus the model would likely over-caveat correct sources, so design the prompt wording before spending an eval. | `src/lib/rag/rag-source-block.ts:126-198`; PR #1051 audit item 8 | 2026-07-22 |
| #035 | P3 | rec | Threshold-conflict detection covers only 3 params | `detectThresholdDisagreements` checks only ANC, WBC, and platelets paired with withholding verbs, so cross-source conflicts on medication doses, lithium/thyroid levels, or vital signs go undetected. Deliberately narrow (see the comment at `:469-474`). Broadening changes when an answer is classified `conflicting` and adds warnings — real false-positive risk. Needs new fixtures plus a behaviour review before any change. | `src/lib/evidence.ts:469-574`; PR #1051 audit item 7 | 2026-07-22 |
| #036 | P3 | rec | No explicit `is_public` visibility flag on documents | Public-corpus visibility is implicit: `owner_id IS NULL` on an `indexed` document (`resolveSearchScope`). The `metadata.public_corpus` marker is written by the promotion migrations but never used as a retrieval filter. Promotion is unconditional on `clinical_validation_status`, so unverified documents are publicly searchable — compensated by keeping `unverified_source` in the frontend-visible warning set. A hard schema flag touches RLS and the clinical-risk-gated retrieval RPCs; weigh against the existing compensating control before acting. | `supabase/schema.sql:61-108`; `src/lib/search-scope.ts:181-236`; PR #1051 audit item 3 | 2026-07-22 |
Expand Down
26 changes: 24 additions & 2 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { NextConfig } from "next";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { withSentryConfig } from "@sentry/nextjs";
import { buildSecurityHeaders, resolveRuntimeFlags } from "./src/lib/security-headers";
import { expectedSupabaseProject } from "./src/lib/supabase/project";

Expand Down Expand Up @@ -82,12 +83,21 @@ const nextConfig: NextConfig = {
turbopack: {
root: projectRoot,
},
webpack(config) {
webpack(config, { webpack }) {
// 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 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. See instrumentation-client.ts.
config.plugins.push(
new webpack.DefinePlugin({
__SENTRY_ENABLED__: JSON.stringify(Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN?.trim())),
}),
);
return config;
},
async headers() {
Expand Down Expand Up @@ -127,4 +137,16 @@ const nextConfig: NextConfig = {
},
};

export default withOptionalBundleAnalyzer(nextConfig);
// Source-map upload + same-origin tunnel. Org/project/authToken are optional at
// build time — without them the SDK still instruments, but maps are not uploaded.
// tunnelRoute keeps client envelopes on connect-src 'self' (no *.sentry.io in CSP).
export default withOptionalBundleAnalyzer(nextConfig).then((config) =>
withSentryConfig(config, {
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
widenClientFileUpload: true,
tunnelRoute: "/monitoring",
silent: !process.env.CI,
}),
);
Loading
Loading