diff --git a/.agents/skills/catalog.json b/.agents/skills/catalog.json index 258033a69..b544b2f6b 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 b465f2d3e..a9aaca2cb 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,48 +1,18 @@ -<<<<<<< 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 -Complete every outstanding finding from the 2026-07-19 repository-wide review sweep with the smallest safe patches, clear ownership boundaries, and local/offline proof before any provider-backed gate. +Complete every outstanding finding from the 2026-07-19 repository-wide review sweep with the smallest safe patches, clear ownership boundaries, minimal regression risk, and local/offline proof before any provider-backed gate. -## Non-negotiables +## Current constraints & Non-negotiables -- Keep unrelated work out of each patch. +- Current branch: `work` (or feature branch). - Use Node 24/npm 11 and the existing npm lockfile before trusting typecheck, lint, tests, build, or installed Next docs. +- Before any Next/framework code change, read the relevant installed guide in `node_modules/next/dist/docs/`. +- Keep unrelated work out of each patch. Do not combine unrelated fixes in one patch unless they share the same verification surface. - Do not change prompts, retrieval ranking, source formatting, auth, RLS, deployment secrets, or provider configuration unless that batch explicitly requires it. - Do not run Supabase/OpenAI/live eval/hosted CI/release commands without explicit approval. -- Keep the formatting drift as a separate final pass so behavior diffs stay reviewable. +- Keep the formatting drift (spanning 27 files) as a separate final pass so behavior diffs stay reviewable. ## Issue map @@ -61,84 +31,20 @@ Complete every outstanding finding from the 2026-07-19 repository-wide review sw ## Execution sequence -### Batch 0 — Verification prerequisites first +### Batch 0 — Restore local verification prerequisites -**Why first:** All later fixes need representative local checks. +**Purpose:** Make all later checks representative before changing behavior. -**Patch scope:** Prefer no repo file changes. If environment docs are needed, keep them docs-only. - -**Steps** +**Smallest actions** -1. Switch to Node 24/npm 11 using `.nvmrc`, host tool manager, or the repo container image. +1. Switch local runtime to Node 24 and npm 11 using `.nvmrc`, host tool manager, or the repo container image. 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 +4. Confirm dependency/tool presence: - `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** @@ -147,7 +53,10 @@ Complete every outstanding finding from the 2026-07-19 repository-wide review sw 2. `npm run typecheck` 3. Stop and triage if either fails before touching product code. -**Regression guard:** No lockfile or dependency edits unless `npm ci` proves the manifest/lockfile is inconsistent. +**Risk control** + +- No lockfile or dependency edits unless `npm ci` proves the manifest/lockfile is inconsistent. +- Do not run provider-backed checks. ### Batch 1 — Answer `summaryMode` contract and scope integrity @@ -162,7 +71,7 @@ Complete every outstanding finding from the 2026-07-19 repository-wide review sw **Smallest safe implementation** -1. Add a shared summary-mode validation invariant: `summaryMode: true` requires `documentId`; `documentIds` must be absent or exactly `[documentId]`; filters are rejected unless a product owner explicitly wants filtered summaries. +1. Add a shared summary-mode validation invariant: `summaryMode: true` requires exactly one `documentId`; `documentIds` must be absent or exactly `[documentId]`; filters are rejected unless a product owner explicitly wants filtered summaries. 2. Prefer making non-stream `/api/answer` call the same governed `summarizeDocument(documentId, ownerId, { signal })` path as streaming. If wiring the response parity is unexpectedly large, choose the safer fallback: reject non-stream `summaryMode` with a clear 400. 3. In stream route, validate the summary invariant before calling `resolveSearchScope`, so conflicting `documentIds` cannot satisfy scoping for another document. 4. Keep all other answer behavior unchanged: no prompt changes, ranking changes, citation formatting changes, or telemetry schema expansion unless needed for the tests. @@ -192,113 +101,12 @@ Complete every outstanding finding from the 2026-07-19 repository-wide review sw **Smallest safe implementation** -1. Add `"release/**"` to PR Policy `pull_request_target.branches`. +1. Add `"release/**"` to PR Policy `pull_request_target.branches` so PR Policy mirrors CI protected PR 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** @@ -313,7 +121,7 @@ Ask before running any of these: - Composite action `uses:` lines are scanned. - Existing pinned local actions still pass. -**Regression guard:** Do not change workflow permissions, token use, checkout refs, or hosted CI behavior beyond static coverage. +**Regression guard:** Do not change workflow permissions, token use, checkout refs, or hosted CI behavior beyond static coverage. Do not call GitHub APIs or rerun hosted CI without explicit confirmation. ### Batch 3 — UI/accessibility remnants @@ -328,7 +136,7 @@ Ask before running any of these: **Smallest safe implementation** 1. Convert the mockup `Table 3` anchor to a real in-page link only if a stable target exists; otherwise use a styled `span`. -2. Change favourites “Recent” and “Add favourite” to native `disabled` buttons, or replace them with non-button status pills if they are roadmap-only. +2. Change favourites “Recent” and “Add favourite” to native `disabled` buttons, or replace them with non-button status pills if they are roadmap-only. Keep existing visual treatment as much as possible. 3. Replace `title="Soon"` on differential density controls with visible accessible “Coming soon” text, or remove the disabled toggle until the feature exists. **Focused proof** @@ -341,6 +149,7 @@ Ask before running any of these: - No href-less actionable-looking anchor remains in the touched mockup. - Unavailable controls no longer create misleading keyboard/AT affordances. - No new route/state behavior is introduced. +- Do not redesign the surfaces or introduce new state/routing/features. ### Batch 4 — `.npmrc allow-scripts=true` decision @@ -361,7 +170,7 @@ Ask before running any of these: 1. `rg -n "allowScripts|allow-scripts" . --glob '!node_modules'` 2. `npm run check:runtime` -3. Any install check only after Node 24 is active. +3. Any install check only after Node 24 is active. Use `npm run format:check` only after dependencies are installed. **Done criteria** @@ -392,6 +201,7 @@ Ask before running any of these: - `format:check` passes. - The commit contains no semantic edits. +- Keep this as its own commit/PR. Do not mix with clinical/RAG or CI logic changes. ## Final local handoff gate @@ -404,14 +214,7 @@ 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 +Ask before running any of these (do not run without explicit confirmation): - `npm run check:supabase-project` - `npm run check:production-readiness` @@ -420,26 +223,11 @@ 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 +## Recommended execution order & PR split + +**Order:** Batch 0 (prerequisites) → Batch 1 (clinical RAG) → Batch 2 (CI governance) → Batch 3 (UI/a11y) → Batch 4 (`.npmrc` decision) → Batch 5 (formatting) → Final handoff gate. This 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. + +**PR Split:** 1. PR A: Batch 0 docs/prerequisite proof only if environment setup requires repo documentation; otherwise no PR. 2. PR B: Batch 1 answer `summaryMode` contract and tests. @@ -449,10 +237,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 cd09b5fd6..3a9067dda 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-25 — PR #1188 / execute-audit-remediation-plan exhaustive PR review + +- Branch/ref: PR #1188 / `execute-audit-remediation-plan` +- HEAD: 8b8639113925601e1687bfe4f1f29c44a4308b61 +- Scope: Exhaustive review of PR #1188 and its audit maintainability remediation changes, including RAG ranking regression checks, API boundary verification, and UI component integrity. +- Outcome: REJECT PR merge until P0/P1 findings are fixed. Found P0 critical build regression: unresolved Git merge conflict markers across 10 source/test files and 2 documentation files (notably nested markers in RAG-governed `src/app/api/answer/route.ts:L6-L18` and `src/app/api/upload/route.ts:L249`). Found P1 unit test regression: unconditional `useRouter()` invocation in `NavigationBackButton` fails in unit tests (such as `tests/privacy-ui.test.ts`) when App Router context is unmounted. Found P2 scope divergence: 110 files modified in PR history containing unrelated checkpoint artifacts. RAG ranking core logic (`src/lib/rag/rag.ts`) remained correctly unchanged per RAG governance rules. +- Checks: `git diff origin/main...HEAD --stat`; `npm install`; `npm run check:rag:fixtures && npm run eval:rag:offline` (failed on build transformation due to conflict markers in `api/answer/route.ts`); targeted `grep_search` across entire codebase for `<<<<<<< ` conflict markers; read-only inspection of RAG boundaries and navigation components. No provider-backed checks, live database mutations, or live PR edits were executed. diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index fc1995b64..44c427986 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,7 +137,6 @@ 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 @@ -232,14 +144,11 @@ Move resolved rows here with the resolution date and a one-line outcome. Keep th | 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 | diff --git a/docs/search-chrome-behaviour.md b/docs/search-chrome-behaviour.md index a3f9236ed..54835a095 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/scripts/check-docs-links.mjs b/scripts/check-docs-links.mjs index 6493a303e..c83e59538 100644 --- a/scripts/check-docs-links.mjs +++ b/scripts/check-docs-links.mjs @@ -100,7 +100,7 @@ function stripSuffixes(value) { if (result.startsWith("./")) result = result.slice(2); // Drop #anchor fragments and :line / :line-line / :line:col suffixes. result = result.replace(/#[^#]*$/, ""); - result = result.replace(/:\d+([-:]\d+)?$/, ""); + result = result.replace(/:[L]?\d+([-:][L]?\d+)?$/i, ""); return result; } diff --git a/scripts/check-github-action-pins.mjs b/scripts/check-github-action-pins.mjs index 539f90a88..9db2b94a1 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/scripts/decompose-indexing-v3.mjs b/scripts/decompose-indexing-v3.mjs new file mode 100644 index 000000000..5b221245d --- /dev/null +++ b/scripts/decompose-indexing-v3.mjs @@ -0,0 +1,66 @@ +import fs from "fs"; + +const indexFile = "supabase/functions/indexing-v3-agent/index.ts"; +const utilsFile = "supabase/functions/indexing-v3-agent/utils.ts"; + +let content = fs.readFileSync(indexFile, "utf-8"); + +const utilsToExtract = [ + "normalizeText", + "tokenize", + "safeRecord", + "compactString", + "uniqueStrings", + "structuredProfileFromMetadata", + "stringArrayFrom", + "textItemsFrom", + "sourceRegionsFromMetadata", + "isLowQualityLabel", + "phraseLabelCandidates", + "sha256Hex", + "sleep", + "normalizeLabel", + "normalizeLabelCandidate", + "canonicalUnitType", + "canonicalFieldType", +]; + +let utilsContent = `// Extracted utilities for indexing-v3-agent\nimport { EXPECTED_EMBED_DIM, type GeneratedLabelCandidate } from "./behavior.ts";\n\n`; + +const regexesToExtract = ["LABEL_STOPWORDS", "GENERIC_LABELS", "CLINICAL_PHRASE_PATTERN"]; + +let modifiedContent = content; + +for (const constName of regexesToExtract) { + const r = new RegExp(`const ${constName} = [\\s\\S]*?;\\n+`, "g"); + const match = modifiedContent.match(r); + if (match) { + utilsContent += `export ` + match[0] + "\n"; + modifiedContent = modifiedContent.replace(r, ""); + } +} + +for (const fn of utilsToExtract) { + const r = new RegExp(`(async )?function ${fn}\\([\\s\\S]*?\\n}\\n+`, "g"); + const match = modifiedContent.match(r); + if (match) { + let replaced = match[0].replace(`function ${fn}(`, `export function ${fn}(`); + replaced = replaced.replace(`async function ${fn}(`, `export async function ${fn}(`); + utilsContent += replaced + "\n"; + modifiedContent = modifiedContent.replace(r, ""); + } +} + +const imports = `import { + ${utilsToExtract.join(",\n ")} +} from "./utils.ts";\n`; + +modifiedContent = modifiedContent.replace( + 'import postgres from "npm:postgres@3.4.7";', + `import postgres from "npm:postgres@3.4.7";\n` + imports, +); + +fs.writeFileSync(utilsFile, utilsContent); +fs.writeFileSync(indexFile, modifiedContent); + +console.log("Decomposed into utils.ts"); diff --git a/src/app/api/answer/route.ts b/src/app/api/answer/route.ts index 306a13640..cd7ee30fe 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 4d92aae2c..f4a8d6f8d 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 0a15b3ec2..d0651fa77 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -19,7 +19,6 @@ import { ShieldAlert, Square, UploadCloud, - WifiOff, Wrench, } from "lucide-react"; import { @@ -63,10 +62,6 @@ import { ClinicalMobileSidebar, } from "@/components/clinical-dashboard/ClinicalSidebar"; import { - SetupChecklist, - UploadPanel, - IndexingMonitor, - IngestionQualityConsole, LibraryHealthStrip, fallbackSetupChecks, hasReadyRequiredPublicSearchConfig, @@ -75,6 +70,7 @@ import { type IngestionQualityReviewItem, } from "@/components/clinical-dashboard/DocumentManagerPanel"; import { GuideDialog, GuideTrigger, UtilityDrawer } from "@/components/clinical-dashboard/dashboard-shell"; +import { SystemNotice, DegradedNotice } from "@/components/clinical-dashboard/dashboard-notices"; import { sanitizeAnswerDisplayText, sanitizeDisplayText } from "@/components/clinical-dashboard/display-text"; import { isPreformattedGroundedAnswer, ScopeAndGovernanceNotice } from "@/components/clinical-dashboard/answer-content"; import { AnswerEmptyState, AnswerProgressStepper, AnswerSkeleton } from "@/components/clinical-dashboard/answer-status"; @@ -152,6 +148,26 @@ const DocumentSearchResultsPanel = dynamic( { ssr: false }, ); +const SetupChecklist = dynamic( + () => import("@/components/clinical-dashboard/DocumentManagerPanel").then((mod) => mod.SetupChecklist), + { ssr: false }, +); + +const UploadPanel = dynamic( + () => import("@/components/clinical-dashboard/DocumentManagerPanel").then((mod) => mod.UploadPanel), + { ssr: false }, +); + +const IndexingMonitor = dynamic( + () => import("@/components/clinical-dashboard/DocumentManagerPanel").then((mod) => mod.IndexingMonitor), + { ssr: false }, +); + +const IngestionQualityConsole = dynamic( + () => import("@/components/clinical-dashboard/DocumentManagerPanel").then((mod) => mod.IngestionQualityConsole), + { ssr: false }, +); + import { clearLegacyRecentQueries, demoRecentQueryOwnerId, recentQueryStorageKey } from "@/lib/recent-query-storage"; import type { SearchFacets } from "@/components/clinical-dashboard/document-search-results"; import { isWeakRelevance } from "@/components/clinical-dashboard/relevance"; @@ -3054,23 +3070,6 @@ export function ClinicalDashboard({ empty: !answer || (answerRenderModel?.reviewSources.length ?? 0) === 0, }, ] as const; - const renderSystemNotice = (className?: string) => ( - -

- {demoMode - ? "Demo mode is active with three synthetic indexed documents, citations, source cards, image captions, and document links. Synthetic data only; not clinical guidance." - : `Configure .env.local and run supabase/schema.sql before uploading or searching. ${setupWarning}`} -

-
- ); const showAuthPanel = false; const showDegradedNotice = !isOnline || (apiUnavailable && !canRunSearch); const submittedAnswerSearchActive = @@ -3125,28 +3124,6 @@ export function ClinicalDashboard({ differentialsCompareAddonActive, }), ); - const renderDegradedNotice = () => ( - -

- {!isOnline - ? "Reconnect before uploading documents, refreshing source URLs, or generating answers." - : isDeployedClinicalKb() - ? "The app will preserve the current view. If this keeps happening, check your connection and try again shortly." - : "The app will preserve the current view. Retry after confirming the local server, Supabase, OpenAI, and worker setup."} -

-
- ); const setupReadyCount = setupChecks.filter((check) => check.status === "ready").length; const setupCheckCount = setupChecks.length || fallbackSetupChecks.length; const activeUploadWork = @@ -3519,8 +3496,10 @@ export function ClinicalDashboard({ {actionNotice.message} )} - {showDegradedNotice && renderDegradedNotice()} - {showSystemNotice && answer ? renderSystemNotice("hidden sm:block") : null} + {showDegradedNotice && } + {showSystemNotice ? ( + + ) : null}
0 && calc.items.every((item) => item.kind === "checkbox"); } @@ -36,7 +36,7 @@ export function isCheckboxOnly(calc: CalculatorFixture): boolean { * CAGE/SAD PERSONS screen reads as a valid 0 result (started + complete) only * once the user chooses to record it, not merely by opening the scale. */ -export function seedCheckboxDefaults(calc: CalculatorFixture, answers: AnswerMap): AnswerMap { +function seedCheckboxDefaults(calc: CalculatorFixture, answers: AnswerMap): AnswerMap { if (!isCheckboxOnly(calc)) return answers; if (calc.items.every((item) => answers[item.id] !== undefined)) return answers; const next: AnswerMap = { ...answers }; @@ -46,13 +46,13 @@ export function seedCheckboxDefaults(calc: CalculatorFixture, answers: AnswerMap return next; } -export type CalculatorResult = { +type CalculatorResult = { label: string; tone: CalculatorTone; guidance: string; }; -export type CalculatorState = { +type CalculatorState = { answers: AnswerMap; score: number; /** Options-style items answered so far. */ @@ -157,13 +157,13 @@ export function deriveCalculator(calc: CalculatorFixture, answers: AnswerMap): D }; } -export function toggleCheckboxAnswer(answers: AnswerMap, itemId: string): AnswerMap { +function toggleCheckboxAnswer(answers: AnswerMap, itemId: string): AnswerMap { // Toggle between explicit 1 ("Yes") and 0 ("No") rather than clearing to // undefined, so an unticked box stays a recorded negative answer. return { ...answers, [itemId]: answers[itemId] === 1 ? 0 : 1 }; } -export function selectOptionAnswer(answers: AnswerMap, itemId: string, optionIndex: number): AnswerMap { +function selectOptionAnswer(answers: AnswerMap, itemId: string, optionIndex: number): AnswerMap { return { ...answers, [itemId]: answers[itemId] === optionIndex ? undefined : optionIndex }; } @@ -188,7 +188,7 @@ export function useCalculatorState(calc: CalculatorFixture): CalculatorState { /* ---------- tone styling ---------- */ -export const toneChip: Record = { +const toneChip: Record = { success: "border-[color:var(--success-border)] bg-[color:var(--success-soft)] text-[color:var(--success)]", info: "border-[color:var(--info-border)] bg-[color:var(--info-soft)] text-[color:var(--info)]", warning: "border-[color:var(--warning-border)] bg-[color:var(--warning-soft)] text-[color:var(--warning)]", @@ -294,7 +294,7 @@ export function BandLegend({ calc, activeBand }: { calc: CalculatorFixture; acti /* ---------- interactive item controls ---------- */ -export function CheckboxRow({ +function CheckboxRow({ item, checked, onToggle, @@ -357,7 +357,7 @@ export function CheckboxRow({ ); } -export function OptionScale({ +function OptionScale({ item, value, onSelect, @@ -547,14 +547,14 @@ export function formatResultSummary(calc: CalculatorFixture, state: DerivedCalcu * carry bespoke option sets (then items render stacked full labels instead * of numbered chips plus one response key). */ -export function sharedOptionKey(calc: CalculatorFixture) { +function sharedOptionKey(calc: CalculatorFixture) { const optionItems = calc.items.filter((item) => item.kind === "options"); if (optionItems.length < 2) return null; const first = optionItems[0].options; return optionItems.every((item) => item.options === first) ? (first ?? null) : null; } -export function ResponseKey({ calc }: { calc: CalculatorFixture }) { +function ResponseKey({ calc }: { calc: CalculatorFixture }) { const key = sharedOptionKey(calc); if (!key) return null; return ( diff --git a/src/components/clinical-dashboard/answer-status.tsx b/src/components/clinical-dashboard/answer-status.tsx index 559d3791e..679a67fa1 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/dashboard-notices.tsx b/src/components/clinical-dashboard/dashboard-notices.tsx new file mode 100644 index 000000000..4e611d451 --- /dev/null +++ b/src/components/clinical-dashboard/dashboard-notices.tsx @@ -0,0 +1,56 @@ +import { CircleAlert, WifiOff } from "lucide-react"; +import { UtilityDrawer } from "@/components/clinical-dashboard/dashboard-shell"; +import { isDeployedClinicalKb } from "@/lib/clinical-search"; + +export function SystemNotice({ + demoMode, + setupWarning, + className, +}: { + demoMode: boolean; + setupWarning: string | null; + className?: string; +}) { + return ( + +

+ {demoMode + ? "Demo mode is active with three synthetic indexed documents, citations, source cards, image captions, and document links. Synthetic data only; not clinical guidance." + : `Configure .env.local and run supabase/schema.sql before uploading or searching. ${setupWarning}`} +

+
+ ); +} + +export function DegradedNotice({ isOnline }: { isOnline: boolean }) { + return ( + +

+ {!isOnline + ? "Reconnect before uploading documents, refreshing source URLs, or generating answers." + : isDeployedClinicalKb() + ? "The app will preserve the current view. If this keeps happening, check your connection and try again shortly." + : "The app will preserve the current view. Retry after confirming the local server, Supabase, OpenAI, and worker setup."} +

+
+ ); +} diff --git a/src/components/clinical-dashboard/evidence-panels.tsx b/src/components/clinical-dashboard/evidence-panels.tsx index ed597c8a8..346501ad6 100644 --- a/src/components/clinical-dashboard/evidence-panels.tsx +++ b/src/components/clinical-dashboard/evidence-panels.tsx @@ -575,15 +575,13 @@ 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 { diff --git a/src/components/clinical-dashboard/settings-dialog.tsx b/src/components/clinical-dashboard/settings-dialog.tsx index 2cfff3194..1ee287803 100644 --- a/src/components/clinical-dashboard/settings-dialog.tsx +++ b/src/components/clinical-dashboard/settings-dialog.tsx @@ -50,7 +50,6 @@ import { fieldControlWithIcon, fieldIcon, floatingControl, - IconButton, InlineNotice, primaryControl, toggleThumbSurface, @@ -261,19 +260,7 @@ export function SettingsDialog({ return () => window.cancelAnimationFrame(focusFrame); }, [emailEntryOpen]); -<<<<<<< ours const backButton = ; -======= - const backButton = ( - - ); ->>>>>>> theirs const closeButton = (