feat: harden public API limits, server-only env boundaries, and registry corpus sync#488
Conversation
…aries - Fail closed on paid anonymous answer rate limits: drop caller-controlled User-Agent from quota keys, add a global durable anonymous ceiling, and refuse in-memory limiter fallback for the answer bucket outside local dev - Enforce server-only env.ts with a client-safe public-env module, tsx/vitest server-only stubs, and a post-build client-bundle secret scan - Replace monolithic CI with risk-scoped lanes (changes/static-pr/safety/ coverage/build/ui-critical/db-replay) behind a single PR required aggregate; full-run sentinel exercises every lane and force-push diffs fail open - Make registry corpus sync best-effort with hash-gated re-embedding that still refreshes rows on derived-metadata drift without new OpenAI calls - Redact structured error logs via safeErrorLogDetails across routes/seeds, return typed error codes from jsonError, and skip public scope enumeration in favour of the retrieval owner sentinel - Polish mode-home/medication UI (tap targets, scrollable pill rows, portal composer breakpoints) and tag @critical Chromium smokes for the CI lane Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l hardening Main independently landed evolved versions of the risk-scoped CI system (#454, e8481dc), the phone composer UX (#456/#470), the client-env module (#457), and the medication filter strip redesign. Resolution favours main's landed implementations and keeps this branch's novel work: fail-closed anonymous answer limits, server-only env enforcement (run-tsx hook + vitest stub + bundle secret scan), structured jsonError codes, search-scope public early return, registry corpus best-effort sync, and safe-buffer extraction. Dropped this branch's superseded duplicates (lib-based ci-change-scope, pr-local plan, eval-rag-offline.ts) and ported the force-push diff fallback into main's ci-change-scope.mjs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…test config Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mposer UX Main's #470 places the composer above the Start here region on phone mode homes; this branch's pre-merge assertion encoded the superseded layout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Updates to Preview Branch (claude/code-review-42a2c3) ↗︎
Tasks are run on every commit but only new migration files are pushed.
View logs for this Workflow Run ↗︎. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR updates runtime verification, client-secret detection, API error handling, rate limiting, registry corpus synchronization, search behavior, prescribing UX, operational documentation, and automated coverage. ChangesApplication hardening
Estimated code review effort: 4 (Complex) | ~60 minutes Caution Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional.
❌ Failed checks (1 error, 1 warning)
✅ Passed checks (9 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 48cabd9b87
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
tests/private-access-routes.test.ts (1)
441-441: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winWidened error assertions (
toEqual→toMatchObject) lose exact-shape verification for error envelopes.This PR's stated goal is sanitized, structured API errors (Line: error logging and HTTP responses use sanitized details and stable codes per PR summary). Switching these ~25 assertions from strict
toEqualtotoMatchObjectis likely needed because the envelope now includes an additional field (e.g. a stablecode), but it also means these tests would silently pass if an unexpected/sensitive extra field leaked into the response alongsideerror. Given the explicit privacy/sanitization focus of this PR, consider asserting the full expected envelope (e.g.{ error: "...", code: "..." }) instead of a partial match, so a future regression that adds a leaked field is caught.Also applies to: 464-464, 657-657, 671-671, 709-709, 751-751, 860-860, 879-879, 936-936, 1289-1289, 1899-1899, 1963-1963, 2208-2208, 2381-2381, 2445-2445, 2463-2463, 2561-2561, 2684-2684, 2713-2713, 2744-2744, 2927-2927, 2958-2958, 3240-3240, 3340-3340, 3893-3893
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/private-access-routes.test.ts` at line 441, Widened error assertions no longer verify the complete sanitized response envelope. Update the listed assertions in tests/private-access-routes.test.ts, including the payload(response) checks, to use strict equality with the expected error message and stable code fields, matching the structured API error shape and preventing unexpected fields from being accepted.tests/client-secret-surface.test.ts (1)
53-85: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winImport-chain scan misses dynamic
import()calls.
localImportsonly inspects top-levelImportDeclaration/ExportDeclarationstatements. A client module that reachesenv.tsviaawait import("@/lib/env")(or any dynamic import inside a function) would silently bypassserverEnvImportChains(), defeating the purpose of this guard test.♻️ Suggested addition to also catch dynamic imports
function localImports(file: string) { const cached = importCache.get(file); if (cached) return cached; const text = readFileSync(file, "utf8"); const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true); const specifiers: string[] = []; - for (const statement of source.statements) { - if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) { - ... - } - } + const visit = (node: ts.Node) => { + if ( + ts.isCallExpression(node) && + node.expression.kind === ts.SyntaxKind.ImportKeyword && + node.arguments[0] && + ts.isStringLiteral(node.arguments[0]) + ) { + specifiers.push(node.arguments[0].text); + } + ts.forEachChild(node, visit); + }; + for (const statement of source.statements) { + if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) { + ... + } + } + visit(source);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/client-secret-surface.test.ts` around lines 53 - 85, The localImports function only detects static imports and exports, so dynamic import() dependencies are omitted from the chain scan. Extend its TypeScript AST traversal to find CallExpressions whose expression is the import keyword, collect string-literal module specifiers, and resolve them through resolveLocalImport alongside existing specifiers; ensure nested dynamic imports are detected, not just top-level statements.tests/differentials-route.test.ts (1)
93-114: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy liftConsole.error assertions verify the mock's own hardcoded log call, not the real registry-corpus logging path.
bestEffortSyncDifferentialRowsis fully mocked here and reimplements theconsole.error("[differentials] registry corpus sync failed", ...)call itself (Line 106-109), duplicating the shape of the real implementation insrc/lib/registry-corpus.ts(console.error(\[${scope}] registry corpus sync failed`, safeErrorLogDetails(error))). The assertions at Lines 249-252 and 282-285 therefore just confirm the mock does what it's told, not that the production sanitized-logging path (safeErrorLogDetails) actually produces this shape. Ifregistry-corpus.ts`'s real scope string or sanitization changes, this test won't catch the regression.Consider exercising the real
bestEffortSyncDifferentialRows/shared best-effort helper (mocking only the embedding call, as done intests/registry-corpus.test.ts) rather than mocking the whole module's error-handling behavior.Also applies to: 228-286
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/differentials-route.test.ts` around lines 93 - 114, Replace the fully mocked bestEffortSyncDifferentialRows implementation in mockRuntime with the real registry-corpus error-handling path, mocking only the embedding dependency as in registry-corpus.test.ts. Remove the mock’s hardcoded console.error and return values, then configure the embedding call to fail so assertions at the affected test cases verify the production safeErrorLogDetails-based sanitized logging and real scope string.src/app/api/medications/[slug]/route.ts (1)
114-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider extracting the seed-retry pattern into a shared helper.
The catch/log/refetch/throw-only-if-still-missing logic here duplicates the pattern already extracted as
fetchOwnerDifferentialRowsWithSeedfor the differentials route. Sincemedication-seed.tsdoesn't expose an equivalent wrapper, this shell is hand-rolled inline — and, per the PR objective of unifying seed-failure handling across routes, is likely duplicated again in the sibling routes (differentials/[slug],differentials/presentations/[slug],registry/records/[slug]) not included in this batch.♻️ Suggested extraction into medication-seed.ts
+export async function fetchOwnerMedicationRowWithSeed( + supabase: AdminClient, + ownerId: string, + slug: string, + fetchRecord: () => Promise<MedicationRecordRow | null>, +): Promise<MedicationRecordRow | null> { + let row = await fetchRecord(); + if (!row) { + const { count, error: countError } = await supabase + .from("medication_records") + .select("id", { count: "exact", head: true }) + .eq("owner_id", ownerId); + if (countError) throw new Error(countError.message); + if ((count ?? 0) === 0) { + let seedError: unknown = null; + try { + await ensureMedicationsSeeded(supabase, ownerId); + } catch (error) { + seedError = error; + console.error("[medications] auto-seed failed", safeErrorLogDetails(error)); + } + row = await fetchRecord(); + if (!row && seedError) throw seedError; + } + } + return row; +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/api/medications/`[slug]/route.ts around lines 114 - 122, Extract the medication seed retry behavior into a shared helper in medication-seed.ts, matching fetchOwnerDifferentialRowsWithSeed: perform the initial fetch, attempt ensureMedicationsSeeded, log failures with safeErrorLogDetails, refetch afterward, and rethrow the seed error only when the record remains missing. Update the medications route and sibling record routes to use this helper instead of duplicating the inline seedError/catch/refetch logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/database-drift-detection.md`:
- Around line 102-108: Update the document’s “Last updated” header to 2026-07-10
so it matches the newly added update.
In `@docs/operator-apply-july8-batch.md`:
- Around line 24-27: Update the obsolete migration ID references in the R17
warning and manual-repair note to consistently use 20260708160001, including the
reference around the manual repair instructions.
In `@src/components/clinical-dashboard/medication-prescribing-workspace.tsx`:
- Around line 91-94: Update the Safety capability description near the
ShieldCheck configuration from “Avoid and cautions” to “Contraindications and
cautions” (or “Avoidance and cautions”) to provide grammatical user-facing copy.
In `@src/lib/http.ts`:
- Around line 32-39: Harden publicErrorCode so PublicApiError details.code is
only returned when it matches a stable lowercase snake_case identifier;
otherwise fall back to the existing status-based handling, including
internal_error for server failures. Add or update tests covering arbitrary error
class names such as TypeError and valid stable codes.
---
Nitpick comments:
In `@src/app/api/medications/`[slug]/route.ts:
- Around line 114-122: Extract the medication seed retry behavior into a shared
helper in medication-seed.ts, matching fetchOwnerDifferentialRowsWithSeed:
perform the initial fetch, attempt ensureMedicationsSeeded, log failures with
safeErrorLogDetails, refetch afterward, and rethrow the seed error only when the
record remains missing. Update the medications route and sibling record routes
to use this helper instead of duplicating the inline seedError/catch/refetch
logic.
In `@tests/client-secret-surface.test.ts`:
- Around line 53-85: The localImports function only detects static imports and
exports, so dynamic import() dependencies are omitted from the chain scan.
Extend its TypeScript AST traversal to find CallExpressions whose expression is
the import keyword, collect string-literal module specifiers, and resolve them
through resolveLocalImport alongside existing specifiers; ensure nested dynamic
imports are detected, not just top-level statements.
In `@tests/differentials-route.test.ts`:
- Around line 93-114: Replace the fully mocked bestEffortSyncDifferentialRows
implementation in mockRuntime with the real registry-corpus error-handling path,
mocking only the embedding dependency as in registry-corpus.test.ts. Remove the
mock’s hardcoded console.error and return values, then configure the embedding
call to fail so assertions at the affected test cases verify the production
safeErrorLogDetails-based sanitized logging and real scope string.
In `@tests/private-access-routes.test.ts`:
- Line 441: Widened error assertions no longer verify the complete sanitized
response envelope. Update the listed assertions in
tests/private-access-routes.test.ts, including the payload(response) checks, to
use strict equality with the expected error message and stable code fields,
matching the structured API error shape and preventing unexpected fields from
being accepted.
🪄 Autofix (Beta)
✅ Autofix completed
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: efa811bd-5bf9-4ee4-a272-69327a7380e5
📒 Files selected for processing (65)
.prettierignoreAGENTS.mddocs/branch-review-ledger.mddocs/database-drift-detection.mddocs/deployment-architecture.mddocs/operator-apply-july8-batch.mddocs/rag-hybrid-findings-and-todo.mddocs/tenancy-defense-in-depth-review.mdpackage.jsonscripts/check-client-bundle-secrets.mjsscripts/check-july8-live-batch.tsscripts/check-retrieval-owner-migration.tsscripts/ci-change-scope.mjsscripts/enable-server-only-stub.mjsscripts/register-server-only.mjsscripts/run-eval-safe.mjsscripts/run-tsx.mjsscripts/run-vitest.mjssrc/app/api/answer/stream/route.tssrc/app/api/differentials/[slug]/route.tssrc/app/api/differentials/presentations/[slug]/route.tssrc/app/api/differentials/route.tssrc/app/api/medications/[slug]/route.tssrc/app/api/registry/records/[slug]/route.tssrc/app/globals.csssrc/components/clinical-dashboard/DocumentManagerPanel.tsxsrc/components/clinical-dashboard/medication-prescribing-workspace.tsxsrc/components/clinical-dashboard/medication-record-page.tsxsrc/components/mode-home-template.tsxsrc/lib/api-rate-limit.tssrc/lib/app-modes.tssrc/lib/citations.tssrc/lib/differential-seed.tssrc/lib/env.tssrc/lib/extractors/document.tssrc/lib/http.tssrc/lib/medication-seed.tssrc/lib/owner-scope.tssrc/lib/public-api-access.tssrc/lib/registry-corpus.tssrc/lib/registry-seed.tssrc/lib/safe-buffer.tssrc/lib/search-scope.tssupabase/drift-allowlist.jsontests/api-rate-limit-fallback.test.tstests/api-validation-contract.test.tstests/client-secret-surface.test.tstests/differentials-route.test.tstests/http-error-response.test.tstests/medications-route.test.tstests/owner-scope.test.tstests/private-access-routes.test.tstests/public-api-access.test.tstests/registry-corpus.test.tstests/registry-records-route.test.tstests/retrieval-owner-filter-guard.test.tstests/safe-buffer.test.tstests/search-interaction-route.test.tstests/search-scope.test.tstests/stubs/server-only.tstests/tsx-server-only-runner.test.tstests/ui-smoke.spec.tstests/ui-tools.spec.tstests/worker-safe-logging.test.tsvitest.config.mts
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. Fixes Applied SuccessfullyFixed 5 file(s) based on 4 unresolved review comments. Files modified:
Commit: The changes have been pushed to the Time taken: |
Fixed 5 file(s) based on 4 unresolved review comments. Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/ui-smoke.spec.ts (1)
1930-1940: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRedundant fixed-delay re-assertion.
toContainTextalready polls/retries internally, so the second identical assertion afterwaitForTimeout(600)mostly just re-verifies via a hardcoded sleep rather than an event-driven wait. If the intent is to confirm the status doesn't later flip (e.g., due to a delayed async fetch), consider documenting that intent in a comment, or useexpect.pollwith a timeout tied to the specific async event instead of an arbitrary sleep, to reduce flakiness risk.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/ui-smoke.spec.ts` around lines 1930 - 1940, Replace the fixed waitForTimeout and redundant second toContainText assertion in the differential source-status test with an event-driven expect.poll or another wait tied to the delayed async request. If retaining the second verification to ensure the status does not change, document that intent and use a bounded, meaningful timeout rather than a hardcoded sleep.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@tests/ui-smoke.spec.ts`:
- Around line 1930-1940: Replace the fixed waitForTimeout and redundant second
toContainText assertion in the differential source-status test with an
event-driven expect.poll or another wait tied to the delayed async request. If
retaining the second verification to ensure the status does not change, document
that intent and use a bounded, meaningful timeout rather than a hardcoded sleep.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6715b195-ece6-48f5-bb1a-b5baec7128e0
📒 Files selected for processing (8)
docs/branch-review-ledger.mddocs/database-drift-detection.mddocs/operator-apply-july8-batch.mdsrc/components/clinical-dashboard/medication-prescribing-workspace.tsxsrc/lib/http.tstests/http-error-response.test.tstests/ui-smoke.spec.tstests/ui-tools.spec.ts
✅ Files skipped from review due to trivial changes (2)
- docs/operator-apply-july8-batch.md
- docs/branch-review-ledger.md
🚧 Files skipped from review as they are similar to previous changes (5)
- docs/database-drift-detection.md
- tests/http-error-response.test.ts
- src/lib/http.ts
- tests/ui-tools.spec.ts
- src/components/clinical-dashboard/medication-prescribing-workspace.tsx
# Conflicts: # docs/branch-review-ledger.md
All actionable threads are resolved on the current head; dismissing the stale blocking review for verified merge readiness.
# Conflicts: # docs/branch-review-ledger.md # src/app/api/answer/stream/route.ts # tests/differentials-route.test.ts # tests/ui-tools.spec.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1c510876b7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| process.exit(1); | ||
| } | ||
|
|
||
| const tsxCli = fileURLToPath(import.meta.resolve("tsx/cli")); |
There was a problem hiding this comment.
Preserve tsx resolution for secondary worktrees
When an eval/package script runs from a secondary git worktree that relies on a sibling or hoisted node_modules rather than its own install, this direct import.meta.resolve("tsx/cli") throws before any fallback can run. The previous eval runner explicitly walked ancestor and sibling worktrees for the tsx CLI, but those eval scripts now delegate through this new runner, so commands like npm run eval:rag fail before the server-only stub is installed unless every worktree has a local install. Moving the old resolver logic into run-tsx.mjs would keep the new stub behavior without regressing the documented multi-worktree workflow.
Useful? React with 👍 / 👎.
…runner (#493) * fix(worker): boot ingestion worker through the server-only-aware tsx runner Dockerfile.worker ran `node node_modules/tsx/dist/cli.mjs worker/index.ts` directly. worker/index.ts imports src/lib/env, which begins with `import "server-only"` (added in #488). Under bare tsx that import throws, so the production ingestion worker container crash-loops on boot. Route the entrypoint through scripts/run-tsx.mjs, which registers the server-only stub hook so the import resolves outside the Next bundler. Guard it in tests/tsx-server-only-runner.test.ts: the worker CMD must route through run-tsx.mjs and never invoke bare tsx, so this cannot regress. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(worker): assert the exact worker CMD exec vector, not substrings Addresses CodeRabbit review on #493: parse the JSON exec-form CMD and assert the full ["node","scripts/run-tsx.mjs","worker/index.ts"] vector so bare-tsx variants (["tsx", …], ["npx","tsx", …], or reordered args) cannot slip past substring checks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Summary
anon:answer:global) caps aggregate spend across rotated/spoofed network identities; theanswerbucket refuses the per-instance in-memory limiter fallback outside local no-auth mode so limiter outages return 503 before provider generation starts.src/lib/env.tsis nowimport "server-only"; client components use the landedclient-envmodule. Standalone tsx entrypoints route throughscripts/run-tsx.mjs(anode:modulehook that stubsserver-only), vitest aliases it to a stub, andnpm run buildnow ends with a client-bundle secret-surface scan (scripts/check-client-bundle-secrets.mjs) that fails the build if service-role/OpenAI markers reach.next/staticorpublic/.jsonErrorreturnsmessage/code/optionalrequestIdalongsideerror; route/seed error logging goes throughsafeErrorLogDetails(path/url/secret/email redaction) instead of raw messages, stacks, and owner ids.status='indexed'+retrieval_owner_matchesguards (asserted by test).safeBufferFromvalidates base64 image payloads in the PDF extractor; seed-failure handling unified across differentials/medications/registry routes; migration-name references corrected (20260708160001_retrieval_owner_matches_fail_closed).min-h-tapon filter/tab chips, scrollable pill rows with masked overflow, richer medication prompts/capability pills wired to suggested searches, clearer back-link on medication record pages,@criticaltags on the core Chromium smokes.changedFilesFromRangeno longer fails the wholechangesjob on a force-push with an unreachable base — it falls through to the full-run sentinel.Reconciliation note: main independently landed evolved versions of the risk-scoped CI system (#454, e8481dc), the phone composer UX (#456/#470),
client-env(#457), and the medication filter strip (#477). This branch's overlapping implementations were dropped in favour of main's during theorigin/mainmerge; only the novel hardening above ships.Verification
npm run verify:pr-local— component gates run individually instead (see Notes):verify:cheap✅ (runtime, action pins, sitemap, type-scale, lint, typecheck, 1520 vitest passed / 1 skipped),format:check✅,eval:rag:offline✅ (57 offline contract tests). Localnpm run builddeliberately skipped: building in the shared checkout corrupts the live dev server (repo-documented hazard); CI's build lane runs build + client-bundle secret scan.npm run verify:ui— Chromium suite against the local dev server: 130/132. During this run one real regression was caught and fixed (this branch's pre-merge phone-layout assertion contradicted main's landed fix(mobile): keep mode-home composer hero-centred on phones and never let it vanish #470 hero-composer UX; aligned to main's assertion, now green). The remaining 2 failures ("newer routed differential context", "mode home heroes identical sizing on mobile") reproduce identically on pure origin/main locally (Windows scrollbar/vw geometry) — environmental, outside this diff, and not in the@criticallane.npm run test:e2e:critical(CI's UI gate): 8/8 passed.npm run eval:retrieval:quality— 36/36,failed_cases=0:npm run eval:rag -- --limit 15+eval:quality -- --rag-only— not applicable: no change to answer generation, the synthesis prompt, or answer post-processing (answer-path changes are logging/rate-limit only; retrieval scoping covered by the 36/36 live eval above)npm run check:production-readiness— READY, 5 PASS, projectClinical KB Database (sjrfecxgysukkwxsowpy)npm run check:deployment-readiness— not run locally; no hosting/startup change (build script only appends the post-build secret scan). CI's boot smoke runs on main.Clinical Governance Preflight
Clinical KB Database(sjrfecxgysukkwxsowpy) — confirmed by production-readiness checkserver-onlyenv enforcement + client-bundle secret scan + import-chain test (tests/client-secret-surface.test.ts)Notes
origin/mainwas merged into this branch (never rebased) to reconcile with the 17 commits that landed since the branch base; conflicts resolved favouring main's landed implementations everywhere both sides built the same feature.answerbucket's fail-closed behavior also applies to authenticated subjects in production: a durable-limiter outage returns 503 on the answer path by design.🤖 Generated with Claude Code