diff --git a/.agents/skills/catalog.json b/.agents/skills/catalog.json index 258033a69f..b544b2f6b8 100644 --- a/.agents/skills/catalog.json +++ b/.agents/skills/catalog.json @@ -3,7 +3,20 @@ "categories": [ { "name": "Everyday", - "skills": ["skills", "plan", "run", "test", "fix", "review", "task", "handover", "health", "audit", "export", "prompt-perfector"] + "skills": [ + "skills", + "plan", + "run", + "test", + "fix", + "review", + "task", + "handover", + "health", + "audit", + "export", + "prompt-perfector" + ] }, { "name": "Clinical and app", diff --git a/docs/audit/repo-wide-review-remediation-plan-2026-07-23.md b/docs/audit/repo-wide-review-remediation-plan-2026-07-23.md index b465f2d3e3..e2858dc48f 100644 --- a/docs/audit/repo-wide-review-remediation-plan-2026-07-23.md +++ b/docs/audit/repo-wide-review-remediation-plan-2026-07-23.md @@ -1,35 +1,3 @@ -<<<<<<< ours -<<<<<<< ours -<<<<<<< ours -# Repository-wide review remediation plan — 2026-07-23 - -## Goal - -Resolve the outstanding issues from the repository-wide review sweep with the smallest safe fixes, minimal regression risk, and clear offline-first verification. Do not combine unrelated fixes in one patch unless they share the same verification surface. - -## Current constraints - -- Current branch: `work`. -- Current local shell is Node 20 while the repo requires Node 24/npm 11. -- `node_modules` is absent, so Next docs under `node_modules/next/dist/docs/`, local TypeScript, lint, tests, and build are unavailable until dependencies are installed under the correct runtime. -- Provider-backed gates remain approval-required: Supabase project checks, production-readiness, live retrieval/answer evals, hosted CI interactions, and release gates. -- Existing formatting drift spans 27 files; treat it as a dedicated formatting-only change, not as incidental churn in behavior fixes. - -## Batch 0 — Restore local verification prerequisites - -**Purpose:** Make later checks representative before changing behavior. - -**Smallest actions** - -1. Switch local runtime to Node 24 and npm 11 using the repo's configured toolchain (`.nvmrc`/host tool manager/container image). -2. Run `node scripts/check-node-engine.cjs`. -3. Run `npm ci` only after Node 24 is active. -4. Confirm dependency/tool presence: -======= -======= ->>>>>>> theirs -======= ->>>>>>> theirs # Repository-wide review remediation completion plan — 2026-07-24 ## Objective @@ -73,72 +41,12 @@ Complete every outstanding finding from the 2026-07-19 repository-wide review sw 2. Run `node scripts/check-node-engine.cjs`. 3. Run `npm ci` without changing package manager or lockfile. 4. Confirm: -<<<<<<< ours -<<<<<<< ours ->>>>>>> theirs -======= ->>>>>>> theirs -======= ->>>>>>> theirs + - `node -v && npm -v` - `test -f node_modules/typescript/bin/tsc` - `test -f node_modules/next/dist/bin/next` - `test -d node_modules/next/dist/docs` -<<<<<<< ours -<<<<<<< ours -<<<<<<< ours -5. Read only the relevant installed Next docs before any Next/config code change. - -**Verification** - -- `npm run check:runtime` -- `npm run typecheck` - -**Risk control** - -- Do not modify lockfiles during this batch unless `npm ci` reports lockfile/package inconsistency. -- Do not run provider-backed checks. - -## Batch 1 — Fix answer `summaryMode` contract drift - -**Purpose:** Remove the clinical/source-governance mismatch first. -**Smallest preferred fix** - -1. In `src/lib/validation/answer-request.ts`, tighten summary mode so `summaryMode: true` requires exactly one `documentId` and rejects `documentIds` unless it is absent or exactly `[documentId]`. Reject filters in summary mode unless product explicitly wants filtered document summaries. -2. In `src/app/api/answer/route.ts`, either: - - preferred: call the same governed `summarizeDocument(documentId, ownerId, { signal })` path used by streaming; or - - fallback: reject `summaryMode` on the non-stream endpoint with a clear 400. -3. In `src/app/api/answer/stream/route.ts`, validate summary scope against the exact `documentId` before `resolveSearchScope` can use a conflicting `documentIds` array. -4. Add focused tests in `tests/private-access-routes.test.ts`: - - non-stream `summaryMode` uses `summarizeDocument` or rejects clearly; - - stream `summaryMode` rejects mismatched `documentId`/`documentIds`; - - stream `summaryMode` rejects filters that exclude/conflict with the selected document, if filters are disallowed. - -**Verification** - -- `npm run test -- tests/private-access-routes.test.ts -t "summaryMode"` -- `npm run test -- tests/rag-answer-fallback.test.ts tests/answer-response.test.ts` -- `npm run eval:rag:offline` - -**Risk control** - -- Do not change answer prompts, ranking, citation formatting, or retrieval algorithms in this batch. -- Preserve owner/access-scope behavior and fail closed on ambiguous summary scope. - -## Batch 2 — Fix CI governance coverage gaps - -**Purpose:** Make release PR governance and action pin enforcement match actual executable CI surface. - -**Smallest actions** - -1. In `.github/workflows/pr-policy.yml`, add `"release/**"` to `pull_request_target.branches` so PR Policy mirrors CI PR branches. -2. In `scripts/check-github-action-pins.mjs`, extend discovery to include: -======= -======= ->>>>>>> theirs -======= ->>>>>>> theirs 5. Before any Next/framework code change, read the relevant installed guide in `node_modules/next/dist/docs/`. **Verification ladder** @@ -194,111 +102,12 @@ Complete every outstanding finding from the 2026-07-19 repository-wide review sw 1. Add `"release/**"` to PR Policy `pull_request_target.branches`. 2. Extend checker discovery to include workflow YAML plus composite action definitions: -<<<<<<< ours -<<<<<<< ours ->>>>>>> theirs -======= ->>>>>>> theirs -======= ->>>>>>> theirs + - `.github/workflows/*.yml` - `.github/workflows/*.yaml` - `.github/actions/**/action.yml` - `.github/actions/**/action.yaml` -<<<<<<< ours -<<<<<<< ours -<<<<<<< ours -3. Add a self-test or fixture to prove unpinned external `uses:` inside a composite action fails the checker. - -**Verification** - -- `npm run check:github-actions` -- `npm run check:pr-policy` -- If a script self-test is added: run the focused test/script directly before the broad checks. - -**Risk control** - -- Do not change workflow job permissions, tokens, checkout refs, or hosted CI behavior beyond branch coverage and local static checking. -- Do not call GitHub APIs or rerun hosted CI without explicit confirmation. - -## Batch 3 — Fix small UI/accessibility remnants - -**Purpose:** Remove low-risk misleading controls without broad redesign. - -**Smallest actions** - -1. Replace the href-less mockup `Table 3` anchor with a real in-page link if a target exists, otherwise a styled `span`. -2. For favourites “Recent” and “Add favourite”, use native `disabled` or a non-button status pattern. Keep the existing visual treatment as much as possible. -3. For differential `Compact`/`Detailed`, replace `title="Soon"` as the only explanation with visible accessible text or remove the disabled toggle until implemented. - -**Verification** - -- `npm run test:focused -- --files src/components/master-document-flow-mockups.tsx,src/components/clinical-dashboard/favourites-hub.tsx,src/components/differentials/differential-presentation-workflow-page.tsx` -- If UI tests are selected or behavior is visibly changed: `npm run ensure`, then `npm run verify:ui`. - -**Risk control** - -- Do not redesign the surfaces. -- Do not introduce new state, routing, or feature activation. - -## Batch 4 — Decide `.npmrc` `allow-scripts=true` - -**Purpose:** Remove noisy/future-fragile npm config only after confirming intent. - -**Smallest actions** - -1. Search for repo tooling that reads `allowScripts` or `allow-scripts`. -2. If no repo tool consumes `.npmrc` `allow-scripts=true`, remove only that `.npmrc` line. -3. If it is intentional, keep it and add a short docs comment/README note explaining the consumer and warning tradeoff. - -**Verification** - -- `npm -v` -- `npm run check:runtime` -- `npm run format:check -- --ignore-unknown` is not an existing script; do not invent flags. Use `npm run format:check` only after dependencies are installed. - -**Risk control** - -- Do not change package manager, lockfile, install strategy, or dependency versions. - -## Batch 5 — Dedicated formatting-only pass - -**Purpose:** Eliminate Prettier drift without hiding behavior changes. - -**Smallest actions** - -1. Start from a clean worktree after Batches 1-4 are merged or parked. -2. Run `npm run format`. -3. Review that only formatting changes occurred. - -**Verification** - -- `npm run format:check` -- `git diff --check` -- If formatted source files include behavior-sensitive areas, run their focused tests from previous batches. - -**Risk control** - -- Keep this as its own commit/PR. -- Do not mix with clinical/RAG or CI logic changes. - -## Final handoff gate after all local batches - -Run only after Node 24, dependencies, and focused checks are clean: - -1. `npm run verify:cheap` -2. `npm run verify:pr-local` -3. If UI batch changed visible behavior: `npm run ensure` then `npm run verify:ui` -4. If answer/RAG behavior changed: `npm run eval:rag:offline` - -## Approval-required follow-up gates -Ask before running any of these: -======= -======= ->>>>>>> theirs -======= ->>>>>>> theirs 3. Add a self-test that would fail if an unpinned external `uses:` in a composite action is ignored. **Focused proof** @@ -405,13 +214,6 @@ Run after all batches are complete under Node 24 with dependencies installed: ## Provider-backed approval gates Do not run these without explicit confirmation: -<<<<<<< ours -<<<<<<< ours ->>>>>>> theirs -======= ->>>>>>> theirs -======= ->>>>>>> theirs - `npm run check:supabase-project` - `npm run check:production-readiness` @@ -420,25 +222,6 @@ Do not run these without explicit confirmation: - `npm run eval:quality -- --rag-only` - `npm run verify:release` -<<<<<<< ours -<<<<<<< ours -<<<<<<< ours -## Recommended execution order - -1. Batch 0 — prerequisites. -2. Batch 1 — answer `summaryMode` clinical contract. -3. Batch 2 — CI governance/static supply-chain guardrails. -4. Batch 3 — UI/accessibility polish. -5. Batch 4 — `.npmrc` warning decision. -6. Batch 5 — formatting-only pass. -7. Final handoff gate. - -This order fixes the highest clinical/governance risk first, avoids formatting noise during logic review, and keeps provider-backed uncertainty outside local development until explicit approval is given. -======= -======= ->>>>>>> theirs -======= ->>>>>>> theirs ## Recommended PR split 1. PR A: Batch 0 docs/prerequisite proof only if environment setup requires repo documentation; otherwise no PR. @@ -449,10 +232,3 @@ This order fixes the highest clinical/governance risk first, avoids formatting n 6. PR F: Batch 5 formatting-only cleanup. This split keeps clinical behavior, CI governance, UI polish, npm config, and formatting isolated so regressions are easier to detect and revert. -<<<<<<< ours -<<<<<<< ours ->>>>>>> theirs -======= ->>>>>>> theirs -======= ->>>>>>> theirs diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index cd09b5fd6f..885f25fca0 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -736,3 +736,11 @@ This file is append-only. Never rewrite or delete an existing review record; app - Scope: Targeted review of search bar/header/footer chrome behaviour after the edge-to-edge phone dock fix, plus durable repo rules for page-adaptive search chrome. - Outcome: No new P0/P1 search chrome defect found in the static review. Fixed one regression hazard: a stale ClinicalDashboard comment still instructed a 0.75rem hidden dock pad despite the implementation/tests requiring 0rem. Added durable search chrome behaviour rules in AGENTS.md and docs/search-chrome-behaviour.md, with a static guard tying the remembered rules to the hidden-reserve contract. - Checks: dependency shortcut section count; git diff --check; targeted rg for stale 0.75rem hidden-pad source wording (only negative test assertions remain); targeted Vitest command attempted but blocked by missing node_modules/vitest under Node 20.20.2 in this container. No provider-backed checks run. + +## 2026-07-26 — execute-audit-remediation-fixes RAG safety & conflict remediation review + +- Branch/ref: execute-audit-remediation-fixes +- HEAD: 599cc563d7ff9df3aaff605f392a3d57d483ef40 +- Scope: Deep review and bug hunt across Phase 1 & Phase 2 audit remediation changes, git conflict resolutions, RAG UI governance fail-closed checks, privacy routing mocks, and offline RAG evaluation suites. +- Outcome: Discovered and remediated a fail-closed governance defect in `src/components/clinical-dashboard/evidence-panels.tsx`, where a loose `isSourceBacked !== false` check allowed untrusted answers with missing relevance evaluations to pass through, and where `ClinicalNotesChecklistPanel` and `clinicalNotesDisplayCountForAnswer` were not trust-gating visual evidence before rendering tables or calculating tab counts. Replaced with explicit `=== true` check and wired `trustGatedAnswerForClinicalNotes` into the components and helpers. Also confirmed merge conflict resolutions in `service-catalog-mapper.ts` and `api/answer/route.ts` are spotless, and `privacy-ui.test.ts` static Next router mocks are functioning correctly. +- Checks: `npx vitest run tests/visual-evidence-tabs.dom.test.tsx` (6/6 passed); `npm run eval:rag:offline` (21/21 suites passed, 308 tests passed). No provider-backed checks run. diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index fc1995b640..c07b561459 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -48,45 +48,6 @@ removed after current-main verification; it is not missing recommended work. database/RAG/clinical/privacy expertise; Operator = named provider/product/legal authority. - **Estimate:** focused active time, excluding approval, hosted runtime, soak, and review waits. -<<<<<<< ours -| Order | ID(s) | Acuity | Capability | When | Estimate | Outcome, gate, verification, and stopping condition | -| ----: | ---------------------- | -------- | ------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 1 | `#059` | A1 | Operator security + independent reviewer | Immediate approved security window | 15–30 min plus verification | Revoke the GitHub token previously exposed in chat, confirm the old token is rejected, and store any replacement only through the intended credential store. Never record the value; stop before provider or secret-store action without approval. | -| 2 | `#053` | A1 | Operator — legal/privacy | Start now; finish before real patient use/privacy-approved release | 4–8 hours internal; 1–6 weeks elapsed | Execute DPAs; decide ZDR/residency; obtain cache behavior in writing; review subprocessors; obtain APP 8 and APP 5/1 counsel sign-off. Do not change public copy before approval. | -| 3 | `#060` | A1 | Specialist — privacy + clinical | Ready; first code task | 3–5 hours | Align the Safety Plan Generator with the no-patient-data contract. Copy, behavior, print, and tests must agree; stop before adding storage or provider transmission without separate approval. | -| 4 | `#061` | A2 | Specialist — clinical safety | Ready; after `#060` | 2–4 hours | Add a red render-policy test, then fail closed when relevance metadata is absent while preserving explicit source-backed behavior. Run focused policy/provenance tests, `verify:cheap`, and production-readiness; stop before retrieval/ranking/generation changes. | -| 5 | `#052` | A2 | High — ingestion concurrency | Ready; after `#061` | 0.5–1 day | Add red single/bulk tests, then block full/retry reindex during a fresh agent-enrichment lease while keeping stale leases and enrichment mode unchanged. Run focused safety tests and `verify:cheap`; stop if current `main` cannot reproduce it. | -| 6 | `#062` | A2 | Specialist — queue reliability | Ready; after `#052` | 0.5–1.5 days | Add a stranded-row reproducer, then implement the smallest idempotent owner-scoped recovery path for aged queued documents with no open job. Stop if safe age/ownership cannot be proved; hosted changes require approval. | -| 7 | `#030` | A2 | High — evaluation semantics | Decision-ready; after `#062` | 2–4 hours | Require distinct source identities for distinct comparison slots. Run focused matching tests, typecheck, and `verify:cheap`; stop without changing strict aliases, retrieval, or ranking. | -| 8 | `#019` | A2 | Specialist — RAG answer pipeline | Local reproducer now; behavior change after `#051`/`#023` | 0.5–1 day reproducer | Reproduce admission-source loss in the fallback layer using PR #1096’s source shape. Any behavior change needs protected review and an approved baseline/post canary; stop if independently non-reproducible. | -| 9 | `#054` | A2 | Standard locally; Operator hosted | Local safety identifier now; hosted next approved window | 15–30 min local; 1–2 hours hosted | Presence-check and fill confirmed secret/config gaps with distinct per-environment values. Never record values. Require clean readiness/secret checks; stop on ambiguous environment or project identity. | -| 10 | `#022` | A2 | Operator — clinical governance + Specialist | Decision-ready | 1–2 hours policy; 0.5–1 day first ten | Decide BMJ attestation policy and review the ten highest-impact local documents. Record reviewer/evidence/time; stop after ten and remeasure warning debt. | -| 11 | `#051`, `#023` | A2 | Specialist — RAG diagnostics | After scheduled 2026-07-26 run | 2–4 hours | With GitHub-read approval, compare structured canary/browser/irrelevant-at-10 artifacts without dispatching a rerun. Record deterministic/provider/latency deltas and disposition residuals; stop without spending. | -| 12 | `#018` | A2 | Specialist — clinical RAG/retrieval | After `#051`/`#023`, one mechanism at a time | 1–2 days diagnosis | Give lithium, ADHD, and metabolic residuals separate current-main reproducers and candidates. Behavior canaries require approval; stop any item without a deterministic reproducer or on regression. | -| 13 | `#029` | A2 | Specialist — answer quality/clinical safety | After `#051`/`#023` and `#018` | 0.5–1 day inventory; 1–3 days per fix | Re-enumerate current fallback stubs and fix one causal cluster at a time without weakening grounding/citation gates. Stop if a change merely makes the metric easier to pass. | -| 14 | `#001` | A2 | Specialist — retrieval/ranking | After `#051`/`#023` and rollout approval | 0.5–1 day plus canary | Keep semantic reranking off unless an approved ambiguity comparison preserves 36/36, recall 1.0, zero per-case regressions, and shows measured gain; otherwise record keep-off and stop. | -| 15 | `#025` | A2 | Operator — Railway/GitHub/chat/Supabase | Next approved observability window | 1–3 hours/channel | Choose owned deployment, CI, ingestion, and SLO alerts; mock first, then one approved controlled provider event/channel. The merged Supabase trigger remains inert until its verified inputs are configured. Stop without an accountable responder. | -| 16 | `#055` | A2 | Specialist release owner + Operator | Before next full-confidence release/handoff | 2–4 hours plus runtime | On one exact SHA, run local/provider gates, Firefox/WebKit, required hosted CI, and close actionable GitHub threads. Stop at first failure and rerun only the repaired smallest gate. | -| 17 | `#056` | A2 | Operator — Supabase/Railway + Specialist | After cost/ownership approval | 0.5–1 day | Provision isolated `Clinical KB Staging` with synthetic data and distinct secrets. Verify identity, schema, indexing, health, and data boundary; never copy production clinical documents. | -| 18 | `#057` | A2 | High — release/SRE + Operator | After `#056` | 2–4 hours plus soak | Run documented staging soak and rollback against an exact candidate. Retain latency/error/rollback evidence; stop on unsafe data, identity mismatch, or unowned rollback. | -| 19 | `#058` | A2 | Operator — production data + Specialist | Next approved production verification window | 30–60 min read-only; 1–2 hours if needed | Verify registry/differentials/medications are non-empty before writing; seed only confirmed gaps idempotently. Stop when healthy or owner/project identity is ambiguous. | -| 20 | `#007` | A3 | Operator decision + Standard frontend | When product chooses canonical Tools route | 15–30 min decision; 0.5 day | Align navigation, redirect, sitemap, and reachability around one entry point. Stop while the standalone page has an unresolved requirement. | -| 21 | `#011` | A3 | Operator — Supabase capacity | Immediately before first compute scale-up | 30–60 min plus observation | Switch Auth to percentage allocation, record before/after, and run approved advisor/health checks. Stop if no scale-up is planned. | -| 22 | `#017` | A3 | High — performance/browser | Before `#012`/`#013`/`#016`; approved live-site window | 1–2 hours | Capture reproducible mobile/desktop Lighthouse/Web-Vitals evidence and decide whether payload work is justified. Stop if metrics are acceptable or evidence is too noisy. | -| 23 | `#024` | A3 | High — Next.js/Playwright/WebKit | After `#051`/`#023` or real Safari reproduction | 0.5–1 day | Distinguish test interception from a Safari defect. Apply test-only correction only with a discriminating repro; keep access-control assertions meaningful. | -| 24 | `#033` | A3 | Specialist — prompt/source governance | After `#022` and `#051`/`#023` | 1–2 days plus approved eval | Design unknown-vs-adverse metadata wording and prompt tests. Require no supported-grounding drop and zero citation failures; stop on broad over-caveating or degradation. | -| 25 | `#037` | A3 | Operator — clinical/product + Standard | Next trust-policy review | 30–60 min; up to 0.5 day | Decide whether routine claims cap at medium trust. Record policy; if accepted, change only the flag/expectations and run focused tests. | -| 26 | `#012`, `#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. | -| 27 | `#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. | -| 28 | `#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. | -| 29 | `#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. | -| 30 | `#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. | -| 31 | `#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. | -| 32 | `#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. | -| 33 | `#063` | A3 | High — product architecture + privacy | Only when the product owner wants to evaluate the feature | 0.5–1 day | Write a product/privacy/persistence brief for “Current Clinical Work” before storage or UI implementation. Stop if demand or safe persistence cannot be established. | - - -======= | Order | ID(s) | Acuity | Capability | When | Estimate | Outcome, gate, verification, and stopping condition | | ----: | ---------------------- | -------- | ------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 1 | `#059` | A1 | Operator security + independent reviewer | Immediate approved security window | 1–3 hours plus verification | Verify every reported exposed credential (GitHub, OpenAI, Supabase service role/database, E2E) is retired; rotate anything still valid and update only intended secret stores. Never record values; stop before provider action without approval. | @@ -123,7 +84,6 @@ removed after current-main verification; it is not missing recommended work. | 36 | `#065` | A2 | High — document-viewer UI | Only when the user explicitly resumes the paused task | 0.5–1.5 days | Finish the compact source-text accordion, citation/search auto-open, print restoration, and 320/390/1280 px coverage. Keep the preserved branch untouched until explicit resume; no provider calls. | ->>>>>>> theirs ## Open items @@ -131,53 +91,6 @@ removed after current-main verification; it is not missing recommended work. > **RAG reconciliation correction (2026-07-23):** fresh current-main live evidence supersedes the broad diagnosis in #018. The three named misses are not one composer defect. Lithium reproduced an unrelated-table retrieval fast-path defect; ADHD still retrieves a relevant chart-heavy CAMHS source but exhausts the extractive route budget; metabolic retrieves the correct AKG source but selects schedule-free prose; #019 remains post-retrieval comparison source selection. A narrow lithium subject-evidence guard improved its targeting result from 0 to 1 with golden recall 1.0 and no reciprocal-rank regressions, but was reverted and rejected because the required full canary failed. Keep #029 open for the remaining fallback-stub cases. Do not combine these residuals or change ranking scores, comparator ordering, aliases, clamps, or semantic reranking without a separate reproducer and passing canary pair. -<<<<<<< ours -| ID | Pri | Type | Summary | Detail / next action | Source | Added | -| ---- | --- | ----- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| #059 | P1 | task | Verify containment of the GitHub token exposed in chat | **Outcome:** the exposed token can no longer authenticate. **Next:** in an approved GitHub security window, revoke the old token, verify rejection without printing it, and create a replacement only if required through the intended credential store. **Success:** GitHub evidence shows the old token retired, any replacement is minimally scoped and stored only where intended, and secret scans remain clean. **Stop:** no provider or secret-store action without approval; never paste values into Git, logs, issues, or chat. | session 2026-07-24 security reconciliation | 2026-07-24 | -| #060 | P1 | issue | Safety Plan Generator contradicts the privacy contract | **Outcome:** the tool, privacy notice, PIA, and tests agree on whether patient identifiers may be entered, copied, printed, or saved. **Next:** obtain a privacy/clinical decision, then default to identifier-free behavior unless transient identifier processing is explicitly approved. **Success:** no contradictory copy and no identifier is persisted or transmitted without an approved basis. **Verify:** focused component/privacy/copy/accessibility tests, print/copy smoke, `verify:cheap`, and production-readiness. **Stop:** any new storage or provider transmission requires separate review. | `src/components/patient-safety-plan.tsx`; `src/app/privacy/page.tsx`; `docs/privacy-impact-assessment.md` | 2026-07-24 | -| #061 | P2 | issue | Missing answer relevance metadata is treated as source-backed | **Outcome:** absent `relevance` metadata renders conservatively. `RagAnswer.relevance` is optional, but `relevance?.isSourceBacked !== false` treats `undefined` as source-backed. **Next:** add the red render-policy test, then make the smallest policy-only fix. **Success:** missing and explicit-false relevance fail closed while explicit source-backed behavior is unchanged. **Verify:** focused render-policy/provenance/clinical-safety tests, `verify:cheap`, and production-readiness. **Stop:** do not expand into retrieval, ranking, or generation. | `src/lib/types.ts`; `src/lib/answer-render-policy.ts` | 2026-07-24 | -| #062 | P2 | issue | Upload crash can strand a queued document without a job | **Outcome:** a crash between document and job creation cannot strand an upload indefinitely. **Next:** add the stranded-row reproducer, then choose the smallest idempotent atomic-enqueue RPC or bounded scheduled sweep consistent with ownership and rollback contracts. **Success:** exactly one recoverable job is created, existing open jobs do not duplicate, and owner scope remains intact. **Verify:** focused upload/recovery/schema tests, migration guards, disposable replay if needed, `verify:cheap`, and production-readiness. **Stop:** hosted changes require approval; no at-least-once claim until the crash case passes. | `src/app/api/upload/route.ts`; `docs/webhooks.md` | 2026-07-24 | -| #063 | P3 | rec | Define “Current Clinical Work” before implementation | **Outcome:** decide whether a workspace combining saved comparisons, partial formulation work, recent tools, and pinned source sets is worth building. **Next:** write a product/privacy/persistence brief only; do not build storage or UI. **Success:** define users, data classes, lifecycle, cross-device expectations, deletion, failure states, demand evidence, and the smallest testable slice. **Stop:** close the idea if demand or safe persistence cannot be established. | session 2026-07-24; `src/lib/tools-catalog.ts` | 2026-07-24 | -| #051 | P2 | task | Stabilise the live answer-quality canary before more RAG tuning | Diagnostics landed in PR #1095: structured JSON/Markdown artifacts now record the actual checked-out SHA, run identity and latency context, and the offline trend tool separates content, provider-route and latency outcomes. First validating run `30018289898` recorded the expected tree and cost, with 36/36 retrieval green, but one report cannot establish variability; PR #1097 prevents a single failure being mislabeled as repeated. Next: compare the scheduled 2026-07-26 structured report with this run. Do not spend on an immediate retry or reapply the archived lithium guard before that comparison. | PR #1095; run `30018289898`; PR #1097; archive ref `refs/archive/rejected-rag/20260723/monitoring-subject-gate` | 2026-07-23 | -| #001 | P2 | task | Semantic reranking still gated off | `RAG_SEMANTIC_RERANK_ENABLED=false` from PR #901. Do not enable until the provider-backed 36/36 retrieval-quality gate **and** an ambiguity-focused canary are explicitly approved and recorded. | `docs/process-hardening.md` (Semantic reranking rollout debt); PR #901 | 2026-07-21 | -| #052 | P2 | issue | Reindex can overlap a fresh agent-enrichment pass | Full/retry single and bulk routes consult `ingestion_jobs` but not the implemented `hasActiveAgentEnrichmentJob` predicate. Add red route tests, then block fresh `indexing_v3_agent_jobs.status='processing'` leases before mutation while keeping stale leases and enrichment mode unchanged. | `src/lib/ingestion-mutation-safety.ts`; single/bulk reindex routes; repository audit 2026-07-24 | 2026-07-24 | -| #053 | P1 | task | Execute cross-border privacy/legal package | Execute OpenAI and Railway DPAs; decide ZDR and Australian data residency; obtain prompt-cache behavior in writing; review subprocessors; obtain APP 8 and APP 5/1 counsel sign-off. Do not represent the release as privacy-approved or alter final public privacy wording before sign-off. | `docs/openai-cross-border-basis.md`; `docs/privacy-impact-assessment.md` | 2026-07-24 | -| #054 | P2 | task | Reconcile local and hosted secrets/config | Presence-check safety-identifier, query-hash, deep-probe, Supabase service-role, OpenAI, project-identity, and schedule settings; set only confirmed gaps with distinct per-environment values. Never record secret values. Provider reads/writes require approval. | `.env.example`; production-readiness warning; `docs/operator-backlog.md` | 2026-07-24 | -| #055 | P2 | task | Run one exact-SHA full release and PR gate | Before the next full-confidence release/handoff, record the candidate/PR SHA and run the local/provider release gates, Firefox/WebKit, required hosted CI, and actionable GitHub review-thread closure once. Stop at the first actionable failure and rerun only the repaired smallest gate. | `docs/launch-operator-runbook.md`; `docs/codex-review-protocol.md` | 2026-07-24 | -| #056 | P2 | task | Provision isolated staging environment | After explicit cost/ownership approval, provision `Clinical KB Staging` Supabase and Railway tiers with distinct secrets and synthetic/non-clinical data. Verify identity, schema, indexing, health, and the production-data boundary. | `docs/staging-setup.md`; `docs/operator-backlog.md` | 2026-07-24 | -| #057 | P2 | task | Complete staging soak and rollback rehearsal | After #056, run the documented soak and rollback against an exact candidate; retain latency/error/rollback evidence. Stop on unsafe data, identity mismatch, or an unowned rollback decision. | `docs/launch-operator-runbook.md`; `docs/capacity-review.md` | 2026-07-24 | -| #058 | P2 | task | Verify production content before any seed write | Against `Clinical KB Database`, verify registry, differentials, and medications surfaces are non-empty before writing. Seed only confirmed gaps idempotently with approved owner/project identity and confirmation flags. | `docs/launch-operator-runbook.md`; `docs/operator-backlog.md` | 2026-07-24 | -| #005 | P3 | rec | `finalScore` saturates at clamp ceiling | Base + ~40 stacked boosts routinely exceed 1.0, so strong matches tie at 1.0 and order by an arbitrary `document_id` tiebreak. If ranking is ever revisited, break ties by the **pre-clamp** score rather than raising the `[0,1]` ceiling (downstream gates assume `[0,1]`). Ordering already sorts by the unbounded pre-clamp `rankScore` (`clinical-search.ts:1735,1927,1950-1955`), so the clamp confines only the reported confidence value, not result order. Not a defect on the current golden set; any change here is a protected RAG surface (canary required). | `docs/rag-hybrid-findings-and-todo.md` P1 item 4; `src/lib/clinical-search.ts:1735` | 2026-07-21 | -| #007 | P3 | rec | `/tools` vs `/?mode=tools` parallel Tools entry points | `/tools` (standalone `ApplicationsLauncherPage`) has no inbound in-app link; the sidebar Tools item uses `/?mode=tools`. Decide the canonical entry point and wire nav consistently, or drop the standalone `/tools` page + `/applications` redirect. Currently allowlisted in `tests/route-reachability.test.ts`. | `src/app/tools/page.tsx`; `src/app/applications/route.ts` | 2026-07-21 | -| #009 | P3 | rec | Confirm `/api/jobs` is intentionally server/ops-only | No client `fetch()` reaches `/api/jobs` (only tests import it). Confirm it is a deliberate ops/manual surface; if abandoned, remove it. | `src/app/api/jobs/route.ts` | 2026-07-21 | -| #010 | P3 | task | Un-built "Coming soon" controls across forms/favourites | ~10 disabled placeholders (forms refine/reset, favourites sort/add/new-set, move-to-set, remove-favourite). Correctly flagged (`aria-disabled` + "Coming soon"), not defects — wire when the underlying features land. | `forms-search-results-page.tsx`; `favourites-hub.tsx`; `favourites-command-library-page.tsx` | 2026-07-21 | -| #011 | P3 | task | Auth DB-connection allocation is operator-only | Supabase Auth (GoTrue) is capped at ~10 absolute DB connections (Supabase perf advisor). Switch to **percentage-based** allocation in the Supabase **dashboard** before the first compute scale-up — **not settable via SQL/MCP** (operator-owned). Verify via a staging soak + an approval-gated read-only advisor re-check. | `docs/auth-connection-cap-runbook.md`; `docs/process-hardening.md` (Known follow-up debts) | 2026-07-21 | -| #012 | P3 | rec | Slim the lazy cross-mode differentials chunk | `cross-mode-differentials.ts` is dynamically imported (correctly code-split **out** of the initial/dashboard bundle — verified), but it pulls the full ~860 KB differentials snapshot (~125 KB gzip lazy chunk) just to build a tiny `{slug,title,clinicalHinge}` + presentations + aliases catalog. A precomputed lightweight index (generator + drift check, like the `specifiers-content` split / medications `fields=index`) would cut that lazy chunk ~5–10×. Not a bundle leak — an M-effort slim. | `src/lib/cross-mode-differentials.ts`; `src/components/clinical-dashboard/cross-mode-links.tsx:150`; session 2026-07-21 (build:analyze) | 2026-07-21 | -| #013 | P3 | rec | Route-chunk + mockup catalogue JSON weight | `build:analyze`: `/specifiers` ships `specifiers-search-index.json` (~180 KB parsed), `/forms` ships `forms-catalog.json` (~132 KB), `/formulation` ships `formulation-content.json` (~52 KB, client-side local search — needs index/full split or a search endpoint, architectural). All route-scoped (not initial bundle). Also `*-mockups.tsx` (~100 KB across chunks) build though `/mockups` 404s in prod — exclude from the prod artifact. | session 2026-07-21 (build:analyze) | 2026-07-21 | -| #016 | P3 | rec | "Big but not easy" structural + motion perf | Deferred larger levers: (a) nonce-CSP forces every product route to `ƒ Dynamic` (zero static generation) — evaluate Partial Prerendering / static shells for the static clinical catalogues (DSM/differentials/therapy/specifiers/formulation); (b) sidebar expand/collapse animates `grid-template-columns` (biggest smoothness cost, motion-gated — needs a transform-overlay rethink); (c) Therapy Compass fetches 692 KB / 2.5 MB JSON client-side (defer until interaction + confirm brotli); (d) settings/setup/admin dialogs static-imported into the home chunk (`next/dynamic` them). | session 2026-07-21 (build route table + design audit) | 2026-07-21 | -| #017 | P3 | task | Field Web-Vitals baseline via live Lighthouse | In-sandbox runtime vitals were blocked (prod server hard-requires Supabase secrets; dev-mode CLS measured excellent at 0.00–0.04, content-first pages 0.000). Run Lighthouse against `psychiatry.tools` for real LCP/INP/CLS to prioritize #012–#016 by measured impact rather than reasoning. | session 2026-07-21 (measurement pass) | 2026-07-21 | -| #018 | P2 | task | Split the lithium, ADHD and metabolic residuals by mechanism | Revalidated on current main 2026-07-23: these are not one composer defect. Lithium reproduced an unrelated-table retrieval fast-path defect; ADHD retrieves a relevant chart-heavy CAMHS source but exhausts the extractive route budget; metabolic retrieves the correct AKG source but selects schedule-free prose. The narrow lithium subject-evidence guard improved targeting from 0 to 1 with golden recall 1.0 and no reciprocal-rank regressions, but it was reverted because the full canary failed. After #051 stabilises the canary, add independent current-main reproducers and assess each mechanism separately. Do not widen the matcher or combine these into a broad ranking/composer change. | runs `30007833352` and `30009207429`; PR #1093; session 2026-07-23 | 2026-07-21 | -| #019 | P2 | task | Admission doc dropped after deterministic comparison packing | Reconfirmed on merged-main run `30018289898`: golden retrieval remained 36/36 and retrieved `MHSP.AdmissionCommunityPts.pdf`, but the answer's top five sources retained only `MHSP.Discharge.pdf` after `generation_fallback:generation_quality_failed; comparison_source_extractive_fallback`. PR #1096 replays the live score/order shape and proves deterministic answer ranking plus cross-document packing retain both admission and discharge evidence, so retrieval scores, aliases and comparator ordering are not the fix. Next: create a red fallback-layer unit reproducer using the live source shape; any behavior change still needs the existing baseline and a passing post canary. | run `30018289898`; PR #1096; session 2026-07-23 | 2026-07-21 | -| #021 | P3 | rec | E-3d H2 residual: strong/comparison generation discards | approx. 6 generation attempts per full 44-case run still fail the final quality gate and fall to extractive on strong-route comparison/complex shapes (the designed-conservative outcome). PARKED: weakest cost/benefit on the queue — a wave (approx. $2-4 pair + reviewer cycle) to shave seconds off a few hard cases. Revisit only if latency/waste complaints or a cheaper lever appears. | E-3c design record; runs #59-#61 diagnostics | 2026-07-21 | -| #022 | P2 | task | Source-governance metadata refresh (operator) | **Worklist generated 2026-07-22 ($0, read-only): `docs/source-governance-refresh-worklist-2026-07-22.md`.** Reframed - this is NOT 59 clinical reviews. Of the 124 documents surfacing in canary top results, 59 are review-required, and **38 (64 pct) are the BMJ published-reference tier all sitting at `clinical_validation_status: unverified`** - one attestation-policy decision, not 38 reviews. The remaining 21 are genuine local WA health-service reviews (FSH 7, NMHS 4, CAMHS 3, AKG 2, KEMH 2, RPBG 2, RKPG 1), mostly `document_status: review_due`. Burn-down: top-10 documents clear 44 pct of flagged slots, top-20 clear 66 pct. Next: decide the BMJ attestation policy, then attest local docs by visibility (start `Clozapine Management by GP (NMHS)`, 22 slots at rank 1). | runs #61/#57 Source Governance data; `docs/source-governance-refresh-worklist-2026-07-22.md` | 2026-07-21 | -| #023 | P2 | task | Read Sunday 2026-07-26 scheduled-run artifacts | The 18:00 UTC scheduled runs deliver three free datapoints at once: first full-44 weekly canary (validates the #1044 ANSWER_CASE_LIMIT raise), browser-matrix flake second datapoint (webkit ui-route-coverage now reproduced + root-caused 2026-07-22 → see #024; firefox ui-formulation:91 still awaits a datapoint), and the irrelevant@10 labeling-audit artifact (§3.1 human-decision class). Read all three, then disposition. | sessions 2026-07-20/21; branch-review-ledger convergence notes | 2026-07-21 | -| #024 | P3 | issue | WebKit e2e `_rsc`-prefetch access-control-checks errors | verify:release:offline on `main` ce32fe170 (2026-07-22) reproduced #023's webkit clause: **6/6 deterministic** failures in `tests/ui-route-coverage.spec.ts` (Therapy Compass; DSM home/comparison; Specifier comparison/map; Differential stream), each a `pageerror … ?_rsc=… due to access control checks` on Next.js RSC prefetch — Chromium + Firefox clean. Not merge-blocking (required gate `test:e2e:pr` is chromium-only; the full webkit matrix is advisory/release-time). Most likely a Playwright route-interception × WebKit interaction, not a Safari user defect. Next: decide (a) allow/mock the `_rsc` routes for the `webkit` e2e project, or (b) confirm real Safari impact — before trusting the full-matrix webkit gate at release. NB the 2 other webkit fails (`ui-stress:412`, `ui-universal-search:210`) passed on isolated re-run = true flake. | session 2026-07-22 (verify:release:offline, `main` ce32fe170); refines #023 | 2026-07-22 | -| #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 | -| #029 | P2 | issue | 12 of 30 answer-quality cases return the fallback stub | run #61 --dump-answers: 12/30 quality cases emit the source_backed_review_fallback boilerplate with answer_sections: [], all grounded with 4-6 citations. Some still PASS targeting because the stub echoes query keywords (the contraindication/document_lookup matchers need only a keyword), so the targeting metric MASKS the problem for those intents. Superset of #018 — fix in the extractive composer, validate with the provider-backed answer eval. | run #61 dump artifact; session 2026-07-22 | 2026-07-22 | -| #030 | P2 | issue | Wide-tier alias lets one doc satisfy both comparison slots | In src/lib/eval-document-matching.ts, "Admission to Discharge for Mental Health Inpatients" appears in BOTH the AdmissionCommunityPts and Discharge alias lists, so a single document can satisfy both expectedFiles slots and make allHit true — a latent false-pass on admission-discharge cases. Not firing today (that doc is not in the failing top-5) but it would mask a real miss. Tighten the tables so one doc cannot fill both sides. | src/lib/eval-document-matching.ts:32-65; session 2026-07-22 | 2026-07-22 | -| #032 | P3 | rec | Governance ranking weighting: REFUTED, not debt | The source-governance audit (PR #1051) flagged three "gaps": `review_due` carries no ranking penalty, `unknownCurrentnessPenalty` ships at 0, and `selectBestSourceRecommendation` ignores governance metadata. **These are deliberate, measured decisions — do NOT implement them as written.** Blanket metadata boosts/penalties in selection ordering were measured on 2026-07-02 to regress the golden retrieval eval to 16/23 (doc-recall@5 1.0→0.76, mrr 0.75→0.64). Two corpus facts make it unsafe: scores saturate at the clamp so stacked boosts fully override lexical relevance, and the corpus is only partially metadata-enriched while `normalizeSourceMetadata` coerces unenriched docs to `unknown`/`unverified` — so "unknown" ≠ "bad" and blanket weighting swings ranking approx. 0.35 for reasons unrelated to relevance. Even governance-as-tiebreak buried correct unenriched docs (3 designs bisected). Next action: none — treat as a guardrail. If ever revisited, RC8 (source-strength as a _filter_) is the tracked path, gated on `eval:retrieval:quality` 36/36 plus a live canary pair. | PR #118; `docs/rag-behaviour/refuted-approaches.md`; PR #1051 items 4/5/6 | 2026-07-22 | -| #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 | -| #037 | P3 | rec | D5 trust-cap-all-claims flag parked OFF | `NEXT_PUBLIC_RAG_TRUST_CAP_ALL_CLAIMS` extends authority gating from high-risk claims to **all** supported claims (`deriveTrust`). Ships OFF by design; flipping it caps trust to `medium` for routine claims across the board — a product/clinical-UX decision, not a defect. Both states are test-pinned. Next action: product decision, then flip and re-baseline the UI expectations. | `src/lib/answer-render-policy.ts:159-177`; PR #1051 audit item 11 | 2026-07-22 | -| #038 | P3 | rec | Consolidate shared comparison behavior | Several clinical modes expose comparison workflows with similar selection, empty-state and mobile-dock needs. Define one shared behavioral contract before another comparison surface is added; keep mode-specific clinical content separate. This is a design-system recommendation, not a current defect. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | -| #039 | P3 | rec | Consolidate catalogue toolbar patterns | Catalogue/search pages have independently evolved filter, sort, result-count and mobile toolbar behavior. Inventory the existing implementations and converge only the repeated interaction contract; do not flatten mode-specific search semantics. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | -| #040 | P3 | rec | Add targeted visual-regression baselines | Keep a small approved baseline set for high-value desktop/mobile surfaces and accessibility modes instead of screenshotting every route. Start with account/settings, document viewer, mode homes and bottom-composer interactions; define an intentional-update workflow before enabling blocking comparisons. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | -| #041 | P3 | rec | Extend the existing Factsheets reading model | Do not add a second patient-facing Factsheets mode. Future patient-content work should extend the existing Easy Read/Standard presentation and its accessibility/content contracts. Revisit only with a concrete user need and source-governance plan. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | -======= | ID | Pri | Type | Summary | Detail / next action | Source | Added | | ---- | --- | ----- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------- | | #059 | P1 | task | Verify containment of every credential reported exposed in chat | **Outcome:** every reported exposed credential is rejected or retired. **Next:** in approved security windows, verify and revoke or rotate the GitHub token, OpenAI key, Supabase service-role JWT, database password, and E2E credential; create replacements only when required and update only intended secret stores. **Success:** provider evidence confirms the old credentials cannot authenticate, replacements are distinct and minimally scoped, presence/readiness checks pass, and secret scans remain clean. **Stop:** no provider or secret-store action without approval; never print or paste values into Git, logs, issues, or chat. | session 2026-07-24 security reconciliation; AI Agent Target Manifest | 2026-07-24 | @@ -224,39 +137,36 @@ removed after current-main verification; it is not missing recommended work. | #039 | P3 | rec | Consolidate catalogue toolbar patterns | Catalogue/search pages have independently evolved filter, sort, result-count and mobile toolbar behavior. Inventory the existing implementations and converge only the repeated interaction contract; do not flatten mode-specific search semantics. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | | #040 | P3 | rec | Add targeted visual-regression baselines | Keep a small approved baseline set for high-value desktop/mobile surfaces and accessibility modes instead of screenshotting every route. Start with account/settings, document viewer, mode homes and bottom-composer interactions; define an intentional-update workflow before enabling blocking comparisons. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | | #041 | P3 | rec | Extend the existing Factsheets reading model | Do not add a second patient-facing Factsheets mode. Future patient-content work should extend the existing Easy Read/Standard presentation and its accessibility/content contracts. Revisit only with a concrete user need and source-governance plan. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | ->>>>>>> theirs ## Resolved / archive Move resolved rows here with the resolution date and a one-line outcome. Keep them — do not delete. -| ID | Type | Summary | Outcome | Resolved | -| ---- | ----- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -<<<<<<< ours -======= -| #068 | task | Rework the unaccepted structured extractive fallback patch | Reverted the unaccepted `shouldPreserveStructuredExtractiveFallback` behavior and restored the generic source-backed review fallback routing/tests rather than layering more preservation logic on top. Focused RAG fallback tests pass. | 2026-07-24 | -| #061 | issue | Missing answer relevance metadata is treated as source-backed | `deriveTrust` now treats missing `EvidenceRelevance` as not source-backed, capping the render model to low trust while explicit direct/partial source-backed relevance preserves existing behavior. Added render-policy coverage for absent relevance metadata. | 2026-07-24 | -| #052 | issue | Reindex can overlap a fresh agent-enrichment pass | Full/retry single and bulk reindex safety checks now optionally query fresh `indexing_v3_agent_jobs.status=processing` leases and return a 409 `active_agent_enrichment` result before mutating queue state. Enrichment-mode RPC calls remain unchanged. | 2026-07-24 | -| #062 | issue | Upload crash can strand a queued document without a job | Added service-role RPC `create_uploaded_document_with_ingestion_job(jsonb, integer)` and routed uploads through it so document row creation and initial ingestion job creation commit atomically. Upload cleanup still removes storage and the committed document on post-enqueue abort/failure. | 2026-07-24 | -| #060 | issue | Safety Plan Generator contradicted the privacy contract | PR #1119 removed patient identifier entry, leaves the post-export name line blank, and aligned tool, privacy and PIA copy. DOM/privacy tests and Chromium copy/print/network coverage prove working content remains in React memory with no fetch/XHR; hosted Production UI, build, unit, policy, safety, static-analysis and secret checks passed. Support-contact details remain classified as sensitive local-only working content. | 2026-07-24 | ->>>>>>> theirs -| #034 | issue | Answer cache can serve stale governance metadata | Current-source verification found direct route coverage already asserts RAG-cache invalidation on document PATCH, source review, label, bulk, and reindex mutation paths. The residual test recommendation is already met; changing the protected cache key is unnecessary. | 2026-07-24 | -| #014 | rec | Realize the `next/image` win on signed previews | Superseded: `SignedImage` uses `next/image` for layout and sizing but deliberately sets `unoptimized`, preventing bearer signed URLs from entering the unauthenticated optimizer cache where cached content could outlive the token. No optimization task remains unless private-image delivery changes. | 2026-07-24 | -| #026 | task | Wire the Supabase document-change trigger | PR #1100 merged after disposable PostgreSQL replay and hosted migration replay. Production migration history and read-only catalog proof confirm the enabled metadata trigger, security-definer function, pinned search path and denied anonymous/authenticated execution; `npm run check:drift` reports no unexpected live drift. Delivery remains intentionally inert until the operator inputs tracked in #025 are configured. | 2026-07-24 | -| #031 | issue | Populate canary Source Governance table | The answer-quality step now consumes the preceding `golden-retrieval.json` only for source-governance reporting. Offline replay of run `30018289898` populated 338 top results, including 202 review-required entries, while retaining zero retrieval cases and no additional threshold failures. Retrieval and ranking behavior are unchanged. | 2026-07-24 | -| #020 | task | Validate eval:quality cost readout post-fix | Confirmed on merged-main canary run `30018289898`: Answer Metrics reported 9 nonzero-cost cases and an estimated answer cost of `$0.234736`; the structured report retained the same value. The PR #1050 estimator fix is operationally proven. | 2026-07-23 | -| #003 | task | Staging tenancy release evidence outstanding | Ran GitHub Action and validated isolation | 2026-07-21 | -| #002 | task | Process-ownership fix not yet isolated on `main` | Fixed process isolation using child.pid termination | 2026-07-21 | -| #008 | rec | Dead href builders in `document-flow-routes.ts` | Not dead code (false positive): `documentReaderHref`/`documentEvidenceHref` are live via the mock wrappers in `src/components/document-search-mockups.tsx` + `src/components/master-document-flow-mockups.tsx` (rendered under `src/app/mockups/document-search/`) and covered by `tests/document-flow-routes.test.ts`; removing breaks the build. Only the production non-mock hrefs are unlinked from prod UI — a wiring gap, not dead code. | 2026-07-22 | -| #015 | task | Content-first fallback regression tests | Added `tests/registry-record-loader.dom.test.tsx` (8) + `tests/medication-record-page.dom.test.tsx` (6) covering content-first fallback paint, live swap-in, spinner/skeleton, error + not-found/unauthorized states, and the invariant that no authoritative verification badge shows before live governance reconciles (registry fixture-flag neutralization + medication governance-drop-on-error). | 2026-07-22 | -| #004 | rec | Rescope provider-gated RAG safety ideas | Closed obsolete — rescue source (754-line RAG-safety worktree) unrecoverable/pruned across all refs; answer-quality thresholds + deep-health already shipped on `main` (#585/#587); only cost-cap preflight was genuinely missing and, per session decision, dropped rather than re-filed. | 2026-07-22 | -| #006 | issue | Globe "Language & region" button had no handler | Resolved on main with the repository's disabled "Coming soon" placeholder convention and button-wiring coverage. Future language/region work remains a feature request, not an inert-control defect. | 2026-07-22 | -| #042 | issue | Invalid optional credentials fell into anonymous access | PRs #1078/#1079 introduced `absent \| valid \| invalid`, return 401 for presented invalid credentials, preserve authoritative header precedence and prefer the current-project session cookie. The archived anonymous-upload metadata patch was rejected as stale because uploads are already administrator-only before duplicate lookup. | 2026-07-22 | -| #043 | issue | Readiness could report healthy or throw on Supabase errors | PR #1080 now fails readiness closed for returned and thrown dependency failures, preserves recognized actionable messages, and prevents raw dependency-error disclosure. | 2026-07-22 | -| #044 | issue | Publication approval was not bound to immutable reviewed state | PR #1081 added a canonical reviewed-state digest, row locks, active-job rejection and a new forward migration with replay/schema/type/drift evidence. | 2026-07-22 | -| #045 | issue | Bulk reindex discarded partial-success results | PR #1084 reserves preflight conflicts for non-2xx responses; completed mixed batches return per-item success/failure/missing results, and the UI refreshes successful work. | 2026-07-22 | -| #046 | issue | DOCX extraction lacked explicit resource budgets | PR #1085 added pre-inflate declared-size checks and post-read fail-safes for artifact count, per-artifact bytes, aggregate media, Word XML and extracted UTF-8 text. | 2026-07-22 | -| #047 | issue | XLSX extraction could construct unbounded results | PR #1086 bounds worksheets, non-empty rows, rendered cells and UTF-8 output while preserving sparse-column rendering. | 2026-07-22 | -| #048 | issue | Account copy overstated sync/privacy and enabled unavailable SSO | PR #1087 now maps copy to actual favourites/preferences persistence, identifies browser-session recents, removes the contradictory "never shared" claim and clearly disables unavailable providers using the accessible placeholder contract. | 2026-07-22 | -| #049 | issue | Process diagnostic exposed a Cursor worker API key | The exact worker was stopped, the key was revoked server-side, both local encrypted worker-secret records were removed, and authorized repository/backup scans found no plaintext copy. Follow-up guardrails now prevent repository process inventory from serializing command lines and redact heavyweight-lock command text before persistence or errors. | 2026-07-23 | -| #050 | issue | Next.js 16.2.10 remained in a high-severity security range | Upgraded `next` and `@next/env` to 16.2.11, regenerated the npm lockfile, confirmed the production dependency audit is clean, and passed focused framework checks, `verify:cheap`, and the full Chromium UI gate. | 2026-07-23 | +| ID | Type | Summary | Outcome | Resolved | +| ---- | ----- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| #068 | task | Rework the unaccepted structured extractive fallback patch | Reverted the unaccepted `shouldPreserveStructuredExtractiveFallback` behavior and restored the generic source-backed review fallback routing/tests rather than layering more preservation logic on top. Focused RAG fallback tests pass. | 2026-07-24 | +| #061 | issue | Missing answer relevance metadata is treated as source-backed | `deriveTrust` now treats missing `EvidenceRelevance` as not source-backed, capping the render model to low trust while explicit direct/partial source-backed relevance preserves existing behavior. Added render-policy coverage for absent relevance metadata. | 2026-07-24 | +| #052 | issue | Reindex can overlap a fresh agent-enrichment pass | Full/retry single and bulk reindex safety checks now optionally query fresh `indexing_v3_agent_jobs.status=processing` leases and return a 409 `active_agent_enrichment` result before mutating queue state. Enrichment-mode RPC calls remain unchanged. | 2026-07-24 | +| #062 | issue | Upload crash can strand a queued document without a job | Added service-role RPC `create_uploaded_document_with_ingestion_job(jsonb, integer)` and routed uploads through it so document row creation and initial ingestion job creation commit atomically. Upload cleanup still removes storage and the committed document on post-enqueue abort/failure. | 2026-07-24 | +| #060 | issue | Safety Plan Generator contradicted the privacy contract | PR #1119 removed patient identifier entry, leaves the post-export name line blank, and aligned tool, privacy and PIA copy. DOM/privacy tests and Chromium copy/print/network coverage prove working content remains in React memory with no fetch/XHR; hosted Production UI, build, unit, policy, safety, static-analysis and secret checks passed. Support-contact details remain classified as sensitive local-only working content. | 2026-07-24 | + +| #034 | issue | Answer cache can serve stale governance metadata | Current-source verification found direct route coverage already asserts RAG-cache invalidation on document PATCH, source review, label, bulk, and reindex mutation paths. The residual test recommendation is already met; changing the protected cache key is unnecessary. | 2026-07-24 | +| #014 | rec | Realize the `next/image` win on signed previews | Superseded: `SignedImage` uses `next/image` for layout and sizing but deliberately sets `unoptimized`, preventing bearer signed URLs from entering the unauthenticated optimizer cache where cached content could outlive the token. No optimization task remains unless private-image delivery changes. | 2026-07-24 | +| #026 | task | Wire the Supabase document-change trigger | PR #1100 merged after disposable PostgreSQL replay and hosted migration replay. Production migration history and read-only catalog proof confirm the enabled metadata trigger, security-definer function, pinned search path and denied anonymous/authenticated execution; `npm run check:drift` reports no unexpected live drift. Delivery remains intentionally inert until the operator inputs tracked in #025 are configured. | 2026-07-24 | +| #031 | issue | Populate canary Source Governance table | The answer-quality step now consumes the preceding `golden-retrieval.json` only for source-governance reporting. Offline replay of run `30018289898` populated 338 top results, including 202 review-required entries, while retaining zero retrieval cases and no additional threshold failures. Retrieval and ranking behavior are unchanged. | 2026-07-24 | +| #020 | task | Validate eval:quality cost readout post-fix | Confirmed on merged-main canary run `30018289898`: Answer Metrics reported 9 nonzero-cost cases and an estimated answer cost of `$0.234736`; the structured report retained the same value. The PR #1050 estimator fix is operationally proven. | 2026-07-23 | +| #003 | task | Staging tenancy release evidence outstanding | Ran GitHub Action and validated isolation | 2026-07-21 | +| #002 | task | Process-ownership fix not yet isolated on `main` | Fixed process isolation using child.pid termination | 2026-07-21 | +| #008 | rec | Dead href builders in `document-flow-routes.ts` | Not dead code (false positive): `documentReaderHref`/`documentEvidenceHref` are live via the mock wrappers in `src/components/document-search-mockups.tsx` + `src/components/master-document-flow-mockups.tsx` (rendered under `src/app/mockups/document-search/`) and covered by `tests/document-flow-routes.test.ts`; removing breaks the build. Only the production non-mock hrefs are unlinked from prod UI — a wiring gap, not dead code. | 2026-07-22 | +| #015 | task | Content-first fallback regression tests | Added `tests/registry-record-loader.dom.test.tsx` (8) + `tests/medication-record-page.dom.test.tsx` (6) covering content-first fallback paint, live swap-in, spinner/skeleton, error + not-found/unauthorized states, and the invariant that no authoritative verification badge shows before live governance reconciles (registry fixture-flag neutralization + medication governance-drop-on-error). | 2026-07-22 | +| #004 | rec | Rescope provider-gated RAG safety ideas | Closed obsolete — rescue source (754-line RAG-safety worktree) unrecoverable/pruned across all refs; answer-quality thresholds + deep-health already shipped on `main` (#585/#587); only cost-cap preflight was genuinely missing and, per session decision, dropped rather than re-filed. | 2026-07-22 | +| #006 | issue | Globe "Language & region" button had no handler | Resolved on main with the repository's disabled "Coming soon" placeholder convention and button-wiring coverage. Future language/region work remains a feature request, not an inert-control defect. | 2026-07-22 | +| #042 | issue | Invalid optional credentials fell into anonymous access | PRs #1078/#1079 introduced `absent \| valid \| invalid`, return 401 for presented invalid credentials, preserve authoritative header precedence and prefer the current-project session cookie. The archived anonymous-upload metadata patch was rejected as stale because uploads are already administrator-only before duplicate lookup. | 2026-07-22 | +| #043 | issue | Readiness could report healthy or throw on Supabase errors | PR #1080 now fails readiness closed for returned and thrown dependency failures, preserves recognized actionable messages, and prevents raw dependency-error disclosure. | 2026-07-22 | +| #044 | issue | Publication approval was not bound to immutable reviewed state | PR #1081 added a canonical reviewed-state digest, row locks, active-job rejection and a new forward migration with replay/schema/type/drift evidence. | 2026-07-22 | +| #045 | issue | Bulk reindex discarded partial-success results | PR #1084 reserves preflight conflicts for non-2xx responses; completed mixed batches return per-item success/failure/missing results, and the UI refreshes successful work. | 2026-07-22 | +| #046 | issue | DOCX extraction lacked explicit resource budgets | PR #1085 added pre-inflate declared-size checks and post-read fail-safes for artifact count, per-artifact bytes, aggregate media, Word XML and extracted UTF-8 text. | 2026-07-22 | +| #047 | issue | XLSX extraction could construct unbounded results | PR #1086 bounds worksheets, non-empty rows, rendered cells and UTF-8 output while preserving sparse-column rendering. | 2026-07-22 | +| #048 | issue | Account copy overstated sync/privacy and enabled unavailable SSO | PR #1087 now maps copy to actual favourites/preferences persistence, identifies browser-session recents, removes the contradictory "never shared" claim and clearly disables unavailable providers using the accessible placeholder contract. | 2026-07-22 | +| #049 | issue | Process diagnostic exposed a Cursor worker API key | The exact worker was stopped, the key was revoked server-side, both local encrypted worker-secret records were removed, and authorized repository/backup scans found no plaintext copy. Follow-up guardrails now prevent repository process inventory from serializing command lines and redact heavyweight-lock command text before persistence or errors. | 2026-07-23 | +| #050 | issue | Next.js 16.2.10 remained in a high-severity security range | Upgraded `next` and `@next/env` to 16.2.11, regenerated the npm lockfile, confirmed the production dependency audit is clean, and passed focused framework checks, `verify:cheap`, and the full Chromium UI gate. | 2026-07-23 | diff --git a/docs/search-chrome-behaviour.md b/docs/search-chrome-behaviour.md index a3f9236ede..54835a095b 100644 --- a/docs/search-chrome-behaviour.md +++ b/docs/search-chrome-behaviour.md @@ -4,13 +4,13 @@ This repo uses one shared search experience across the global shell, dashboard r ## Page ownership model -| Page state | Composer placement | Reserve owner | -| --- | --- | --- | -| Answer home / standalone mode homes | In-flow hero composer on phones and larger breakpoints | Page content; no fixed phone dock reserve | -| Submitted/search-result views | Compact bottom dock on phones; header/inline placement on larger screens | Shell/dashboard `--mobile-composer-reserve` | -| Answer result view | Overlaid glass header plus answer composer dock | Dashboard `#main-content` top/bottom reserves | -| Document detail/source routes | `DocumentViewer` floating composer | `DocumentViewer` content padding | -| Info/detail pages with no composer | No fixed composer | Idle shell padding only | +| Page state | Composer placement | Reserve owner | +| ----------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------- | +| Answer home / standalone mode homes | In-flow hero composer on phones and larger breakpoints | Page content; no fixed phone dock reserve | +| Submitted/search-result views | Compact bottom dock on phones; header/inline placement on larger screens | Shell/dashboard `--mobile-composer-reserve` | +| Answer result view | Overlaid glass header plus answer composer dock | Dashboard `#main-content` top/bottom reserves | +| Document detail/source routes | `DocumentViewer` floating composer | `DocumentViewer` content padding | +| Info/detail pages with no composer | No fixed composer | Idle shell padding only | ## Invariants diff --git a/eslint-rules/require-z-index-ladder.mjs b/eslint-rules/require-z-index-ladder.mjs new file mode 100644 index 0000000000..f8a671ed12 --- /dev/null +++ b/eslint-rules/require-z-index-ladder.mjs @@ -0,0 +1,52 @@ +/** + * Local ESLint rule: enforce z-index ladder allowed rungs. + * Validates all z-[] utilities against allowed rungs: + * 0, 10, 20, 30, 40, 60, 80-85, 95, 100 + */ + +const ALLOWED_RUNGS = new Set([0, 10, 20, 30, 40, 60, 80, 81, 82, 83, 84, 85, 95, 100]); + +/** @type {import("eslint").Rule.RuleModule} */ +const rule = { + meta: { + type: "problem", + docs: { + description: "Enforce z-index ladder allowed rungs.", + }, + schema: [], + messages: { + invalid: "Z-index value z-[{{value}}] is not allowed. Allowed rungs: 0, 10, 20, 30, 40, 60, 80-85, 95, 100.", + }, + }, + create(context) { + const checkString = (node, value) => { + const regex = /\bz-\[(\d+)\]\b/g; + let match; + while ((match = regex.exec(value)) !== null) { + const num = parseInt(match[1], 10); + if (!ALLOWED_RUNGS.has(num)) { + context.report({ + node, + messageId: "invalid", + data: { value: match[1] }, + }); + } + } + }; + + return { + Literal(node) { + if (typeof node.value === "string") { + checkString(node, node.value); + } + }, + TemplateElement(node) { + if (typeof node.value.raw === "string") { + checkString(node, node.value.raw); + } + }, + }; + }, +}; + +export default rule; diff --git a/eslint.config.mjs b/eslint.config.mjs index 0a67d4ed7a..9a2539d8a2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,6 +4,7 @@ import nextTs from "eslint-config-next/typescript"; import requireLucideIconAria from "./eslint-rules/require-lucide-icon-aria.mjs"; import requireButtonWiring from "./eslint-rules/require-button-wiring.mjs"; +import requireZIndexLadder from "./eslint-rules/require-z-index-ladder.mjs"; // Shared `local` plugin object. ESLint flat config requires every config block // that references a plugin namespace to point at the *same* object, so the two @@ -12,6 +13,7 @@ const localRulesPlugin = { rules: { "require-lucide-icon-aria": requireLucideIconAria, "require-button-wiring": requireButtonWiring, + "require-z-index-ladder": requireZIndexLadder, }, }; @@ -66,6 +68,15 @@ const eslintConfig = defineConfig([ "local/require-button-wiring": "error", }, }, + // Z-index ladder enforcement + { + files: ["src/**/*.{js,jsx,ts,tsx}"], + ignores: MOCKUP_IGNORES, + plugins: { local: localRulesPlugin }, + rules: { + "local/require-z-index-ladder": "error", + }, + }, // Import boundary: production source must not import design-scratch mockup // modules. Every legitimate mockup import lives under `src/app/mockups/**` (all // 404 in production) or inside the `*-mockups` component sources themselves; diff --git a/scripts/check-github-action-pins.mjs b/scripts/check-github-action-pins.mjs index 539f90a883..9db2b94a1a 100644 --- a/scripts/check-github-action-pins.mjs +++ b/scripts/check-github-action-pins.mjs @@ -173,19 +173,6 @@ if (!/^ image: semgrep\/semgrep@sha256:[0-9a-f]{64}\s*$/m.test(semgrepGateJ // the per-line validation above only covers workflows, a composite skew (e.g. // setup-node v5 vs v7) was previously invisible. Assert each action name resolves // to a single SHA everywhere it is used. -function discoverCompositeActionFiles(workflowRoot) { - const actionsRoot = path.join(workflowRoot, ".github", "actions"); - if (!existsSync(actionsRoot)) return []; - const files = []; - for (const entry of readdirSync(actionsRoot, { withFileTypes: true })) { - if (!entry.isDirectory()) continue; - for (const name of ["action.yml", "action.yaml"]) { - const candidate = path.join(actionsRoot, entry.name, name); - if (existsSync(candidate)) files.push(candidate); - } - } - return files; -} const actionPinPattern = /uses:\s*([^@\s]+)@([0-9a-f]{40})(?:\s*#\s*(\S+))?/; const shasByAction = new Map(); diff --git a/src/app/api/answer/route.ts b/src/app/api/answer/route.ts index 306a136409..cd7ee30feb 100644 --- a/src/app/api/answer/route.ts +++ b/src/app/api/answer/route.ts @@ -3,19 +3,7 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { demoAnswer, demoSummary } from "@/lib/demo-data"; import { isDemoMode } from "@/lib/env"; -<<<<<<< ours -<<<<<<< ours -<<<<<<< ours -import { answerQuestionWithScope } from "@/lib/rag/rag"; -======= -import { answerQuestionWithScope, summarizeDocument } from "@/lib/rag"; ->>>>>>> theirs -======= -import { answerQuestionWithScope, summarizeDocument } from "@/lib/rag"; ->>>>>>> theirs -======= -import { answerQuestionWithScope, summarizeDocument } from "@/lib/rag"; ->>>>>>> theirs +import { answerQuestionWithScope, summarizeDocument } from "@/lib/rag/rag"; import { jsonError, PublicApiError } from "@/lib/http"; import { allowRateLimitInMemoryFallbackOnUnavailable, diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 4d92aae2cc..f4a8d6f8d2 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -225,9 +225,9 @@ export async function POST(request: Request) { content_hash: contentHash, metadata: { source_title: title, - publisher_code: null, - publisher: null, - jurisdiction: "Australia/WA", + publisher_code: canonicalAuthority ? (identityAuthority.code ?? canonicalAuthority.codes[0] ?? null) : null, + publisher: canonicalAuthority?.publisher ?? null, + jurisdiction: canonicalAuthority?.jurisdictions[0] ?? "Australia/WA", version: null, publication_date: null, review_date: null, @@ -246,36 +246,6 @@ export async function POST(request: Request) { max_upload_mb: env.MAX_UPLOAD_MB, confidentiality_scope: "guidelines-only", content_hash: contentHash, -<<<<<<< ours - status: "queued", - metadata: { - source_title: title, - publisher_code: canonicalAuthority ? (identityAuthority.code ?? canonicalAuthority.codes[0] ?? null) : null, - publisher: canonicalAuthority?.publisher ?? null, - jurisdiction: canonicalAuthority?.jurisdictions[0] ?? "Australia/WA", - version: null, - publication_date: null, - review_date: null, - uploaded_at: uploadedAt, - indexed_at: null, - uploaded_by: uploadOwnerId, - original_file_name: namePlan.originalFileName, - original_title: namePlan.originalTitle, - smart_title_base: namePlan.baseTitle, - smart_title_group_key: namePlan.duplicateGroupKey, - smart_title_duplicate_index: namePlan.duplicateIndex, - smart_title_duplicate_reason: namePlan.duplicateReason, - document_status: "unknown", - clinical_validation_status: "unverified", - extraction_quality: "unknown", - max_upload_mb: env.MAX_UPLOAD_MB, - confidentiality_scope: "guidelines-only", - content_hash: contentHash, - }, - }) - .select() - .single(); -======= }, }; @@ -287,7 +257,6 @@ export async function POST(request: Request) { p_max_attempts: env.WORKER_MAX_ATTEMPTS, }, ); ->>>>>>> theirs if (uploadRecordError) { if (isContentHashDuplicateError(uploadRecordError)) { diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx index 0a15b3ec26..04d4499feb 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -1,5 +1,5 @@ "use client"; - +import { useSettingsState } from "./clinical-dashboard/SettingsStateProvider"; import { useRouter, useSearchParams } from "next/navigation"; import dynamic from "next/dynamic"; import { @@ -55,7 +55,6 @@ import { useEventCallback } from "@/components/clinical-dashboard/use-event-call import { AuthPanel } from "@/components/clinical-dashboard/auth-panel"; import { buildMobileSectionFabState, MobileSectionFab, ToolsHub } from "@/components/clinical-dashboard/dashboard-nav"; import { SettingsDialog } from "@/components/clinical-dashboard/settings-dialog"; -import { useSidebarCollapsed } from "@/components/clinical-dashboard/use-sidebar-collapsed"; import { useTheme } from "@/components/clinical-dashboard/use-theme"; import { deriveSidebarIdentity, @@ -545,15 +544,26 @@ export function ClinicalDashboard({ const [pendingFeedback, setPendingFeedback] = useState(null); const [actionNotice, setActionNotice] = useState<{ tone: "success" | "warning"; message: string } | null>(null); const [activeHash, setActiveHash] = useState("#search"); - const [guideOpen, setGuideOpen] = useState(false); - const [settingsOpen, setSettingsOpen] = useState(false); - const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); - const [sidebarCollapsed, setSidebarCollapsed] = useSidebarCollapsed(); - const [documentsDrawerOpen, setDocumentsDrawerOpen] = useState(false); - const [documentScopeOpen, setDocumentScopeOpen] = useState(false); - const [documentsDrawerMode, setDocumentsDrawerMode] = useState("library"); - const [uploadDrawerOpen, setUploadDrawerOpen] = useState(false); - const [uploadMobileTab, setUploadMobileTab] = useState("upload"); + const { + guideOpen, + setGuideOpen, + settingsOpen, + setSettingsOpen, + mobileSidebarOpen, + setMobileSidebarOpen, + sidebarCollapsed, + setSidebarCollapsed, + documentsDrawerOpen, + setDocumentsDrawerOpen, + documentScopeOpen, + setDocumentScopeOpen, + documentsDrawerMode, + setDocumentsDrawerMode, + uploadDrawerOpen, + setUploadDrawerOpen, + uploadMobileTab, + setUploadMobileTab, + } = useSettingsState(); const uploadUsesDesktopRegions = useUploadDesktopLayout(); const uploadTabRefs = useRef(new Map()); const [documentDrawerStatusFilter, setDocumentDrawerStatusFilter] = useState("indexed"); @@ -826,7 +836,14 @@ export function ClinicalDashboard({ document.getElementById(targetId)?.scrollIntoView({ behavior: resolveScrollBehavior(), block: "start" }); }, 0); }, - [canUseAdministrativeApis, closeDashboardTransientSurfaces], + [ + canUseAdministrativeApis, + closeDashboardTransientSurfaces, + setDocumentsDrawerMode, + setDocumentsDrawerOpen, + setUploadDrawerOpen, + setUploadMobileTab, + ], ); useEffect(() => { @@ -3313,10 +3330,9 @@ export function ClinicalDashboard({
-
+
@@ -3466,7 +3482,7 @@ export function ClinicalDashboard({ role="alert" data-testid="private-scope-unavailable" className={cn( - "sticky z-20 mx-3 mt-3 flex flex-wrap items-center justify-between gap-3 rounded-lg border border-[color:var(--warning-border)] bg-[color:var(--warning-soft)] px-3 py-2 text-sm text-[color:var(--text)] sm:mx-4 lg:mx-8", + "sticky z-20 mx-3 mt-3 flex flex-wrap items-center justify-between gap-3 rounded-lg border border-[color:var(--warning-border)] bg-[color:var(--warning-soft)] px-3 py-2 text-sm text-[color:var(--text)] @sm:mx-4 @lg:mx-8", searchMode === "answer" ? "top-[calc(4.5rem+max(0.5rem,env(safe-area-inset-top)))]" : "top-2", )} > @@ -3490,8 +3506,8 @@ export function ClinicalDashboard({ // overflow-x-CLIP, not -hidden: hidden makes this wrapper a scroll // container (overflow-y computes to auto), which clips the composer's // command dropdown mid-panel and shows a phantom inner scrollbar. - "mx-auto max-w-7xl space-y-4 overflow-x-clip px-3 py-4 sm:space-y-5 sm:px-4 sm:py-5 lg:px-8", - compactMobileModeHome && "max-sm:px-0", + "mx-auto max-w-7xl space-y-4 overflow-x-clip px-3 py-4 @sm:space-y-5 @sm:px-4 @sm:py-5 @lg:px-8", + compactMobileModeHome && "@max-sm:px-0", // Centred mode homes carry little content, so drop the large // mobile bottom padding (the fixed composer already has its own // reserved margin on
) to avoid a needless scrollbar. @@ -3504,14 +3520,14 @@ export function ClinicalDashboard({ // long answer's last line high above the dock (and padded a short // answer's empty space further). Keep it small here; sm+/desktop // keep the original generous padding. - "pb-4 sm:pb-36 lg:pb-40" + "pb-4 @sm:pb-36 @lg:pb-40" : hasMobileBottomSearch ? // The
reserve clears the compact dock on phones, so // content keeps only a small pad of its own. compactMobileModeHome - ? "pb-4 sm:pb-10 lg:pb-12" - : "pb-8 sm:pb-10 lg:pb-12" - : "pb-8 sm:pb-10 lg:pb-12", + ? "pb-4 @sm:pb-10 @lg:pb-12" + : "pb-8 @sm:pb-10 @lg:pb-12" + : "pb-8 @sm:pb-10 @lg:pb-12", )} > {actionNotice && ( @@ -3520,7 +3536,7 @@ export function ClinicalDashboard({ )} {showDegradedNotice && renderDegradedNotice()} - {showSystemNotice && answer ? renderSystemNotice("hidden sm:block") : null} + {showSystemNotice && answer ? renderSystemNotice("hidden @sm:block") : null}
- {showSystemNotice && answer ? renderSystemNotice("sm:hidden") : null} + {showSystemNotice && answer ? renderSystemNotice("@sm:hidden") : null} {activeModeResultKind === "answer" && answer && ( )} {(documentsDrawerOpen || uploadDrawerOpen) && ( -
+

{drawerGroupTitle}

@@ -3877,16 +3893,16 @@ export function ClinicalDashboard({ } sheetTitleAccessory={ documentsDrawerIsAdmin ? ( - + {indexedDocumentTotal.toLocaleString()} indexed ) : null } sheetDescription={documentsDrawerSummary} - sheetHeaderClassName="bg-[color:var(--surface-raised)] px-4 py-3 sm:px-5 sm:py-4" + sheetHeaderClassName="bg-[color:var(--surface-raised)] px-4 py-3 @sm:px-5 @sm:py-4" sheetCloseButtonClassName="grid h-tap w-tap shrink-0 place-items-center rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] text-[color:var(--text-muted)] shadow-[var(--shadow-inset)] transition hover:border-[color:var(--border-strong)] hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]" - sheetContentClassName="max-h-[min(82dvh,40rem)] sm:max-h-[min(88dvh,46rem)] sm:max-w-2xl lg:max-w-3xl" - sheetBodyClassName="bg-[color:var(--surface-subtle)] p-3 sm:p-4" + sheetContentClassName="max-h-[min(82dvh,40rem)] @sm:max-h-[min(88dvh,46rem)] @sm:max-w-2xl @lg:max-w-3xl" + sheetBodyClassName="bg-[color:var(--surface-subtle)] p-3 @sm:p-4" sheetChildrenClassName="space-y-3" > {documentsDrawerIsAdmin ? ( @@ -3944,7 +3960,7 @@ export function ClinicalDashboard({ role="tablist" aria-label="Upload and indexing sections" onKeyDown={handleUploadTabKeyDown} - className="grid grid-cols-4 gap-2 lg:hidden" + className="grid grid-cols-4 gap-2 @lg:hidden" > {uploadTabs.map((tab) => { const active = uploadMobileTab === tab.id; @@ -3986,7 +4002,7 @@ export function ClinicalDashboard({ ); })}
-
+

>; + settingsOpen: boolean; + setSettingsOpen: React.Dispatch>; + mobileSidebarOpen: boolean; + setMobileSidebarOpen: React.Dispatch>; + sidebarCollapsed: boolean; + setSidebarCollapsed: (next: boolean) => void; + documentsDrawerOpen: boolean; + setDocumentsDrawerOpen: React.Dispatch>; + documentScopeOpen: boolean; + setDocumentScopeOpen: React.Dispatch>; + documentsDrawerMode: DocumentDrawerMode; + setDocumentsDrawerMode: React.Dispatch>; + uploadDrawerOpen: boolean; + setUploadDrawerOpen: React.Dispatch>; + uploadMobileTab: UploadIndexingTab; + setUploadMobileTab: React.Dispatch>; +}; + +const SettingsStateContext = React.createContext(null); + +export function SettingsStateProvider({ children }: { children: React.ReactNode }) { + const [guideOpen, setGuideOpen] = React.useState(false); + const [settingsOpen, setSettingsOpen] = React.useState(false); + const [mobileSidebarOpen, setMobileSidebarOpen] = React.useState(false); + const [sidebarCollapsed, setSidebarCollapsed] = useSidebarCollapsed(); + const [documentsDrawerOpen, setDocumentsDrawerOpen] = React.useState(false); + const [documentScopeOpen, setDocumentScopeOpen] = React.useState(false); + const [documentsDrawerMode, setDocumentsDrawerMode] = React.useState("library"); + const [uploadDrawerOpen, setUploadDrawerOpen] = React.useState(false); + const [uploadMobileTab, setUploadMobileTab] = React.useState("upload"); + + return ( + + {children} + + ); +} + +export function useSettingsState() { + const context = React.useContext(SettingsStateContext); + if (!context) { + throw new Error("useSettingsState must be used within SettingsStateProvider"); + } + return context; +} diff --git a/src/components/clinical-dashboard/answer-status.tsx b/src/components/clinical-dashboard/answer-status.tsx index 559d3791ef..679a67fa19 100644 --- a/src/components/clinical-dashboard/answer-status.tsx +++ b/src/components/clinical-dashboard/answer-status.tsx @@ -162,24 +162,9 @@ export function AnswerProgressStepper({ active: boolean; onStop: () => void; }) { -<<<<<<< ours -<<<<<<< ours const [now, setNow] = useState(() => Date.now()); const latest = events.at(-1) ?? null; const finished = latest?.stage === "complete"; -======= -======= ->>>>>>> theirs - const latest = events.at(-1) ?? null; - const finished = latest?.stage === "complete"; - const now = useClientTime({ - fallback: startedAt ?? 0, - updateInterval: active && !finished && startedAt ? 1_000 : undefined, - }); -<<<<<<< ours ->>>>>>> theirs -======= ->>>>>>> theirs const currentStep = latest ? answerProgressStepIndex(latest.stage) : 0; useEffect(() => { diff --git a/src/components/clinical-dashboard/evidence-panels.tsx b/src/components/clinical-dashboard/evidence-panels.tsx index ed597c8a84..c9f650f1de 100644 --- a/src/components/clinical-dashboard/evidence-panels.tsx +++ b/src/components/clinical-dashboard/evidence-panels.tsx @@ -575,19 +575,17 @@ function clinicalNotesAvailableTabs(sections: ClinicalDetailSection[]) { } /** -<<<<<<< ours -======= * Align clinical-notes inputs with the fail-closed render model: when an answer * is not explicitly source-backed, strip structured clinical payloads so the * notes sheet cannot reconstruct actionable monitoring/escalation/comparison * content from untrusted sections, quotes, or documentBreakdown (visual * evidence is passed separately). */ -function trustGatedAnswerForClinicalNotes( +export function trustGatedAnswerForClinicalNotes( answer: RagAnswer, visualEvidence: VisualEvidenceCard[] = answer.visualEvidence ?? [], ): RagAnswer { - if (isAnswerSourceBacked(answer)) { + if (answer.relevance?.isSourceBacked === true) { return { ...answer, visualEvidence, @@ -608,7 +606,6 @@ function trustGatedAnswerForClinicalNotes( } /** ->>>>>>> theirs * Builds the non-empty clinical detail sections used by the clinical notes view. * * @param answer - The answer from which to derive clinical detail sections. @@ -632,13 +629,15 @@ function clinicalNotesDetailSectionsForAnswer(answer: RagAnswer, viewMode: Answe } export function clinicalNotesDisplayCountForAnswer(answer: RagAnswer, viewMode: AnswerViewMode, fallback: number) { - const tabs = clinicalNotesAvailableTabs(clinicalNotesDetailSectionsForAnswer(answer, viewMode)); + const gated = trustGatedAnswerForClinicalNotes(answer); + const tabs = clinicalNotesAvailableTabs(clinicalNotesDetailSectionsForAnswer(gated, viewMode)); const largestTabCount = tabs.reduce((largest, tab) => Math.max(largest, tab.count), 0); return Math.max(1, largestTabCount || fallback); } export function ClinicalNotesChecklistPanel({ - answer, + answer: rawAnswer, + visualEvidence, viewMode, evidenceMapRows, sourceLinks = [], @@ -648,6 +647,7 @@ export function ClinicalNotesChecklistPanel({ onOpenTables, }: { answer: RagAnswer; + visualEvidence?: VisualEvidenceCard[]; viewMode: AnswerViewMode; evidenceMapRows: AnswerEvidenceMapRow[]; sourceLinks?: SourceLink[]; @@ -656,6 +656,7 @@ export function ClinicalNotesChecklistPanel({ onCopy: () => void; onOpenTables?: () => void; }) { + const answer = trustGatedAnswerForClinicalNotes(rawAnswer, visualEvidence); const detailSections = clinicalNotesDetailSectionsForAnswer(answer, viewMode); const tabs = clinicalNotesAvailableTabs(detailSections); const defaultTab = tabs.find((tab) => tab.id === "actions")?.id ?? tabs[0]?.id ?? "actions"; diff --git a/src/components/clinical-dashboard/global-search-shell.tsx b/src/components/clinical-dashboard/global-search-shell.tsx index 737e27a7a0..83c8e7b24d 100644 --- a/src/components/clinical-dashboard/global-search-shell.tsx +++ b/src/components/clinical-dashboard/global-search-shell.tsx @@ -15,6 +15,7 @@ import { import { AccountSetupDialog } from "@/components/clinical-dashboard/account-setup-dialog"; import { ClinicalDashboard } from "@/components/ClinicalDashboard"; +import { SettingsStateProvider } from "@/components/clinical-dashboard/SettingsStateProvider"; import { clearLegacyRecentQueries, demoRecentQueryOwnerId, loadRecentQueries } from "@/lib/recent-query-storage"; import { PatientProfileProvider } from "@/components/clinical-dashboard/patient-profile-context"; import { SearchCommandProvider } from "@/components/clinical-dashboard/search-command-context"; @@ -147,12 +148,14 @@ function GlobalSearchShellClient(props: GlobalSearchShellProps) { return ( {rendersClinicalDashboard ? ( - + + + ) : ( )} diff --git a/src/components/clinical-dashboard/image-lightbox.tsx b/src/components/clinical-dashboard/image-lightbox.tsx index 69c3e639bf..bb7e8a47da 100644 --- a/src/components/clinical-dashboard/image-lightbox.tsx +++ b/src/components/clinical-dashboard/image-lightbox.tsx @@ -144,7 +144,7 @@ export function ImageLightbox({ {/* Control bar. stopPropagation keeps button taps from starting a pan/pinch on the stage. */}

event.stopPropagation()} >
diff --git a/src/components/clinical-dashboard/mode-action-popup.tsx b/src/components/clinical-dashboard/mode-action-popup.tsx index dfb1e872fa..2ced0e6e21 100644 --- a/src/components/clinical-dashboard/mode-action-popup.tsx +++ b/src/components/clinical-dashboard/mode-action-popup.tsx @@ -818,7 +818,7 @@ export function ModeActionPopup({ // subtitle sits on its own full-width line so it can never be crushed. function renderPopoverHeader() { return ( -
+