From d061beec41a906f5a3be761e719be0ecbd9279ea Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 14 Jul 2026 16:57:15 +0000 Subject: [PATCH 1/8] docs: add 2026-07-14 audit remediation plan Sequence every major and sub finding from the multi-skill repo audit into owner-tagged waves with files, acceptance criteria, and verification. Co-authored-by: BigSimmo --- docs/audit-remediation-plan-2026-07-14.md | 458 ++++++++++++++++++++++ docs/operator-backlog.md | 2 + 2 files changed, 460 insertions(+) create mode 100644 docs/audit-remediation-plan-2026-07-14.md diff --git a/docs/audit-remediation-plan-2026-07-14.md b/docs/audit-remediation-plan-2026-07-14.md new file mode 100644 index 000000000..a6b49aaa4 --- /dev/null +++ b/docs/audit-remediation-plan-2026-07-14.md @@ -0,0 +1,458 @@ +# Audit remediation plan — 2026-07-14 + +Plan derived from the multi-skill repository audit of `main` @ `570e6ba` (ledger rows in +[`branch-review-ledger.md`](branch-review-ledger.md)). This document is the sequenced address plan for +every finding: major blockers first, then P2 sub-issues, then P3 cleanup. + +**Rules for executing this plan** + +- Prefer the smallest safe change per finding; do not bundle unrelated domains in one PR. +- Code fixes use `npm run verify:cheap` first, then the smallest domain check, then + `npm run verify:pr-local` before handoff. +- Provider/live actions are **operator-gated** (`⏸`). Do not run them without explicit confirmation. +- Reconcile [`operator-backlog.md`](operator-backlog.md) against + [`launch-operator-runbook.md`](launch-operator-runbook.md) before repeating any historical apply. + +**Legend** + +| Tag | Meaning | +| --- | --- | +| **OWNER:CODE** | Implementable in-repo | +| **OWNER:OPS** | Railway / Supabase / GitHub / OpenAI dashboard or CLI | +| **OWNER:LEGAL** | Privacy officer / counsel | +| **⏸** | Provider or legal confirmation required | +| **Prove** | Smallest acceptance check | + +--- + +## 0. Recommended delivery waves + +Work top-down. Later waves assume earlier majors are either done or consciously deferred. + +```text +Wave A Confirm live/ops truth (doc sync) OWNER:OPS ⏸ +Wave B Launch / privacy blockers OWNER:LEGAL+OPS ⏸ +Wave C Code P1 availability (catalog rate limits) OWNER:CODE +Wave D Security & tenancy P2 OWNER:CODE (+OPS for live A/B) +Wave E Ingestion / recovery P2 OWNER:CODE +Wave F CI / testing / config P2 OWNER:CODE +Wave G Frontend a11y P2 OWNER:CODE +Wave H API contract hygiene P2 OWNER:CODE +Wave I P3 backlog + structure debt OWNER:CODE/OPS +Wave J Release confidence close-out OWNER:OPS ⏸ +``` + +Suggested PR granularity (one theme per PR): + +1. Catalog rate-limit + public DTO redaction +2. Ingestion recovery + commit fallback +3. CI scope + critical safety assert + flake hygiene +4. A11y (describedby / icons / Tab / live region) +5. JSON error taxonomy + admin rate limits +6. Env example / bundle budget / summarize public scope +7. Operator backlog status reconciliation (docs-only) + +--- + +## Wave A — Confirm live truth before acting + +### A1. Reconcile operator backlog vs launch runbook + +| | | +| --- | --- | +| **Finding** | M5 — backlog still `⏳` for drift-codify / prod deploy / worker while runbook says more is already live | +| **Owner** | OWNER:OPS (+ docs edit) | +| **Address** | Diff each backlog row against `launch-operator-runbook.md`, July-8 apply notes, empty `supabase/drift-allowlist.json`, and linked migration list. Flip rows to `✅` / `🔎 verify` / keep `⏳` with evidence date. | +| **Files** | `docs/operator-backlog.md`, optionally a short note in `docs/launch-operator-runbook.md` | +| **Prove** | `⏸ npx supabase migration list --linked`; `⏸ npm run check:drift`; human sign-off that backlog matches live | + +### A2. Confirm July-13 scrub / lexical migration posture + +| | | +| --- | --- | +| **Finding** | Process-hardening still lists July-13 PHI-scrub / lexical items as remaining in places | +| **Owner** | OWNER:OPS | +| **Address** | Confirm whether `20260713103000_scrub_legacy_rag_query_text` (and related) are applied; if not, schedule under Wave B; if yes, mark docs done. | +| **Prove** | `⏸` row counts `rag_queries` not like `redacted-query:%` → expect `0`; lexical probe from process-hardening | + +--- + +## Wave B — Launch / privacy majors (block real-patient use) + +### B1. Close PIA-1 APP 8 / overseas processing basis + +| | | +| --- | --- | +| **Finding** | M1 — Railway Singapore + OpenAI US process incidental PHI queries | +| **Owner** | OWNER:LEGAL + OWNER:OPS | +| **Address** | Execute checklist in [`openai-cross-border-basis.md`](openai-cross-border-basis.md): OpenAI DPA, ZDR eligibility enablement where chosen, Railway DPA/processor record, update PIA status tables and `/privacy` copy only after counsel approval. | +| **Prove** | Status record rows move from `_no_` to dated `_yes_` / approved alternative; PIA-1 no longer High-open | + +### B2. Verify Railway `RAG_QUERY_HASH_SECRET` (PIA-2) + +| | | +| --- | --- | +| **Finding** | M2 — secret verified in GitHub CI, Railway runtime still `🔎 verify` | +| **Owner** | OWNER:OPS | +| **Address** | Set/confirm same ≥16-char value in Railway runtime (prod + staging). Confirm boot smoke and deep readiness. | +| **Prove** | `⏸ npm run check:deployment-readiness` / production health boot; backlog → `✅` | + +### B3. Restore OpenAI quota and run release gates + +| | | +| --- | --- | +| **Finding** | M3 — release gate + golden evals incomplete; prior quota exhaustion | +| **Owner** | OWNER:OPS | +| **Address** | Restore embedding/completions quota → run `eval:retrieval:quality` (36/36) → `eval:quality -- --rag-only` → `npm run verify:release` per [`launch-operator-runbook.md`](launch-operator-runbook.md) §2. | +| **Prove** | Paste summaries into release notes / backlog; canary path in B4 | + +### B4. Eval Canary trust + staging soak + +| | | +| --- | --- | +| **Finding** | M3 leftovers — canary not yet “two greens”; staging soak pending | +| **Owner** | OWNER:OPS | +| **Address** | Provision staging if absent ([`staging-setup.md`](staging-setup.md)); soak (`scripts/soak-test.ts --confirm-staging`, answer p95 ≤ 25 s); run two consecutive Eval Canary greens from `main`. | +| **Prove** | Soak log + two green workflow runs recorded in backlog | + +### B5. Worker image / secret / seed post-deploy confirm + +| | | +| --- | --- | +| **Finding** | Worker redeploy, registry seed, `HEALTH_DEEP_PROBE_SECRET`, auth connection cap | +| **Owner** | OWNER:OPS | +| **Address** | Follow runbook §6 after B3: `reindex:health`, seed registry/differentials, wire ops-digest secrets if desired, flip auth connection allocation **before** vertical scale ([`auth-connection-cap-runbook.md`](auth-connection-cap-runbook.md)). | +| **Prove** | Non-empty Services/Forms; reindex health clear; optional ops-digest cron enabled | + +--- + +## Wave C — Code P1: anonymous catalog availability + +### C1. Rate-limit anonymous public catalogs (M4) + +| | | +| --- | --- | +| **Finding** | Catalog routes short-circuit before rate limits when no auth signal | +| **Owner** | OWNER:CODE | +| **Address** | Always resolve a rate-limit subject (anonymous IP/fingerprint) for medications/registry/differentials catalog GETs; or force lightweight `fields=index` as default for anon and hard-cap full payloads. Prefer both: limiter + default index shape. | +| **Files** | `src/lib/public-api-access.ts`, `src/app/api/medications/route.ts`, `src/app/api/registry/**`, `src/app/api/differentials/**`, rate-limit tests | +| **Prove** | Unit/route tests: unauth GET still calls limiter; optional size cap; `npm run verify:cheap` | + +--- + +## Wave D — Security & tenancy P2 + +### D1. Redact internal fields on authenticated public-document DTOs (S1 / S10 detail) + +| | | +| --- | --- | +| **Finding** | Authed viewers of `owner_id IS NULL` docs receive `storage_path`, `content_hash`, etc. | +| **Owner** | OWNER:CODE | +| **Address** | Apply the same omit-list used for anonymous responses whenever `document.owner_id !== access.ownerId` (or whenever public overlay rows are returned). Keep full fields for the owning user / operators only if product requires. | +| **Files** | `src/app/api/documents/route.ts`, `src/app/api/documents/[id]/route.ts`, focused API tests | +| **Prove** | Authed non-owner GET public doc omits `storage_path` / `content_hash` / `import_batch_id` | + +### D2. Defense-in-depth tenancy CI guard (M6) + +| | | +| --- | --- | +| **Finding** | Service-role single layer; forgotten owner filter is a full leak class | +| **Owner** | OWNER:CODE (+ OWNER:OPS for live A/B) | +| **Address** | Add static CI guard grepping `src/app/api/**` for admin queries on owner-scoped tables without known helpers (`withOwnerReadScope`, `requireOwnerScope`, `.eq("owner_id"` patterns allowlisted). Document remaining intentional exceptions. Schedule `⏸` user A vs B smoke on documents + signed URLs + search. | +| **Files** | new `scripts/check-owner-scope-api.mjs` (or extend existing), `package.json` / `verify:cheap`, tests; update `docs/tenancy-defense-in-depth-review.md` | +| **Prove** | Guard fails on a synthetic unscope fixture; live A/B green | + +### D3. Auth UI: prefer validated user for privilege display (S2) + +| | | +| --- | --- | +| **Finding** | Browser `getSession()` can show signed-in until API 401 | +| **Owner** | OWNER:CODE | +| **Address** | Initialize `AuthProvider` via `getUser()` (or revalidate session with `getUser` before elevating UI), keep optimistic cache only for non-privileged chrome. | +| **Files** | `src/lib/supabase/client.tsx`, auth UI tests | +| **Prove** | Tampered/stale local session → UI not elevated / recovers to signed-out | + +### D4. Public-upload pool principal hygiene (S3) + +| | | +| --- | --- | +| **Finding** | Shared quarantine owner is high blast radius when uploads enabled | +| **Owner** | OWNER:OPS (+ CODE docs) | +| **Address** | Ensure pool UUID has no human password/OAuth sessions; document as non-interactive principal; keep `NEXT_PUBLIC_PUBLIC_UPLOADS_ENABLED` off in prod unless moderation workflow is staffed. | +| **Prove** | Auth user list check + prod env flag audit | + +### D5. Bulk / internal error redaction (S10) + +| | | +| --- | --- | +| **Finding** | Bulk edit returns raw PostgREST/`error.message` | +| **Owner** | OWNER:CODE | +| **Address** | Map to `PublicApiError` / stable codes; log server-side only. | +| **Files** | `src/app/api/documents/bulk/route.ts` | +| **Prove** | Injected update failure → no raw Postgres text in JSON | + +--- + +## Wave E — Ingestion / recovery P2 + +### E1. Fix unreachable commit fallback (I1) + +| | | +| --- | --- | +| **Finding** | `throw` before `upsertIndexQuality` / `deleteStaleIndexGenerationRows` | +| **Owner** | OWNER:CODE | +| **Address** | Restructure `commitDocumentIndexGeneration`: on RPC error, if error is not `lease_lost`, run the documented client fallback then rethrow or return structured failure; never leave unreachable statements. Enable unreachable-code lint/`allowUnreachableCode: false` for worker if feasible. | +| **Files** | `worker/main.ts`, worker/unit tests if present | +| **Prove** | Simulated commit RPC failure invokes cleanup once; typecheck/lint clean | + +### E2. Deduplicate recovery retries per document (I2) + +| | | +| --- | --- | +| **Finding** | `pending` + `failed` → two retry actions → unique index violation | +| **Owner** | OWNER:CODE | +| **Address** | In `buildIngestionRecoveryPlan`, emit at most one retry per `document_id` (prefer existing `pending`, else promote one `failed`). Align `recover-ingestion-queue.ts` with `reindex.ts` status query policy. | +| **Files** | `src/lib/ingestion-recovery.ts`, `scripts/recover-ingestion-queue.ts`, `tests/ingestion-recovery.test.ts` | +| **Prove** | New unit test: pending+failed → `retryCount === 1`; script dry-run does not throw `23505` | + +### E3. Wire reindex eval gate (I3) + +| | | +| --- | --- | +| **Finding** | `decideReindexGate` unused by `scripts/reindex.ts` | +| **Owner** | OWNER:CODE | +| **Address** | After worker settles each generation (or at script end), load eval summary JSON and call `decideReindexGate`; exit non-zero on `NO_GO`. Keep opt-out flag for emergency ops (`--skip-eval-gate`) documented and logged. Optionally relocate module next to the driver under `scripts/`. | +| **Files** | `scripts/reindex.ts`, `src/lib/reindex-eval-gate.ts`, docs in reindex runbook | +| **Prove** | Fixture NO_GO summary → non-zero exit; GO passes | + +--- + +## Wave F — CI / testing / config P2 + +### F1. Expand CI UI scope patterns (C3) + +| | | +| --- | --- | +| **Finding** | `src/lib/app-modes.ts`, `ui-copy.ts`, document-flow / search-navigation helpers do not set `ui_changed` | +| **Owner** | OWNER:CODE | +| **Address** | Extend `uiPatterns` in `scripts/ci-change-scope.mjs` to include those libs (and any other shell-imported copy/routing modules). Add self-tests. | +| **Prove** | `node scripts/ci-change-scope.mjs --json --files src/lib/app-modes.ts` → `ui_changed: true` | + +### F2. Expand CI RAG-eval scope patterns (C4) + +| | | +| --- | --- | +| **Finding** | `clinical-safety.ts`, `privacy.ts`, `public-api-access.ts`, `private-search-scope.ts` miss `rag_eval` | +| **Owner** | OWNER:CODE | +| **Address** | Add those modules to `ragEvalPatterns`; keep test pattern coverage; add self-tests. Fix stale “advisory regression” sentence in `docs/process-hardening.md` L11. | +| **Prove** | Classify `src/lib/clinical-safety.ts` → `rag_eval_changed: true` | + +### F3. Harden `@critical` safety UI assertion (C6) + +| | | +| --- | --- | +| **Finding** | Clozapine demo smoke treats safety findings as optional | +| **Owner** | OWNER:CODE | +| **Address** | For that fixture, require `answer-safety-findings-trigger` and ≥1 `safety-finding-row` unconditionally. | +| **Files** | `tests/ui-smoke.spec.ts` | +| **Prove** | Focused Chromium critical title; intentional findings removal fails the test | + +### F4. Flake ledger → quarantine or fix (C5 / C7) + +| | | +| --- | --- | +| **Finding** | Four known flakes in blocking suite; `@quarantine` unused; sleeps/`networkidle` swallows | +| **Owner** | OWNER:CODE | +| **Address** | For each `flake-ledger.json` entry: fix root cause **or** tag `@quarantine` with owner/date. Replace `waitForTimeout` with condition waits; stop relying on swallowed `networkidle` for readiness. | +| **Prove** | Either ledger empty or every ledger title tagged/fixed; critical/regression green | + +### F5. Align `.env.example` with safe defaults (C1) + +| | | +| --- | --- | +| **Finding** | Example enables `RAG_TEXT_WEAK_OR_RELAXATION=true` | +| **Owner** | OWNER:CODE | +| **Address** | Set example to `false` or comment as opt-in experiment requiring golden eval. Document spend/price env vars (P3) in the same pass if touching the file. | +| **Prove** | Example matches `env.ts` defaults for risky flags | + +### F6. Capture and enforce bundle budget (C2) + +| | | +| --- | --- | +| **Finding** | `enforce: false`, no baseline | +| **Owner** | OWNER:CODE | +| **Address** | On a known-good `npm run build`, run `npm run check:bundle-budget -- --update`, then set `enforce: true`. | +| **Prove** | CI fails on synthetic budget break; passes on baseline | + +--- + +## Wave G — Frontend a11y P2 + +### G1. Fix dead `aria-describedby` (U1) + +| | | +| --- | --- | +| **Finding** | Description span is `aria-hidden="true"` while referenced by `aria-describedby` | +| **Owner** | OWNER:CODE | +| **Address** | Remove `aria-hidden` from description span in `mode-action-popup.tsx` (keep description out of the accessible *name*). | +| **Prove** | Chromium a11y check / axe on action menu; unit DOM assertion | + +### G2. Dynamic Lucide icons `aria-hidden` (U2) + +| | | +| --- | --- | +| **Finding** | ~25 variable `` sites; ESLint rule gap | +| **Owner** | OWNER:CODE | +| **Address** | Add `aria-hidden="true"` at all decorative dynamic icon sites; extend `eslint-rules/require-lucide-icon-aria.mjs` to track LucideIcon variables when feasible. | +| **Prove** | Lint clean; spot-check mode menu / send button | + +### G3. Close mode menu on Tab (U3) + +| | | +| --- | --- | +| **Finding** | Tab leaves `role="menu"` open | +| **Owner** | OWNER:CODE | +| **Address** | In `handleModeOptionKeyDown`, on Tab: `setModeMenuOpen(false)` and allow default focus move. | +| **Files** | `master-search-header.tsx` | +| **Prove** | Keyboard Playwright: open menu → Tab → menu gone | + +### G4. Announce answer readiness (U4) + +| | | +| --- | --- | +| **Finding** | No live region for streaming answer completion | +| **Owner** | OWNER:CODE | +| **Address** | Keep/extend `role="status" aria-live="polite"` through loading → complete; announce start once and “Answer ready” (or equivalent) on completion — not per token. | +| **Files** | `answer-status.tsx` / `ClinicalDashboard.tsx` / `answer-content.tsx` | +| **Prove** | Accessibility test asserts status text transitions | + +--- + +## Wave H — API contract hygiene P2 + +### H1. Unify JSON error envelopes (S4 / S5) + +| | | +| --- | --- | +| **Finding** | Auth and many routes return `{ error }` without `code`/`message` | +| **Owner** | OWNER:CODE | +| **Address** | Route all failures through `jsonError` / `PublicApiError`. Update auth helper first, then upload/document/feedback/demo paths in follow-up commits if too large. | +| **Prove** | Contract tests for 401/400/404 sample paths | + +### H2. Rate-limit authenticated write/admin routes (S6) + +| | | +| --- | --- | +| **Finding** | bulk / labels / table-facts / ingestion quality / eval-cases / jobs lack buckets | +| **Owner** | OWNER:CODE | +| **Address** | Assign buckets (reuse `document_read` or add `document_admin` / `ingestion_admin`); return 429 + `Retry-After`. | +| **Prove** | Mocked exhaustion → 429 | + +### H3. Cap document list offset (S7) + +| | | +| --- | --- | +| **Finding** | Offset max 1_000_000 | +| **Owner** | OWNER:CODE | +| **Address** | Cap to e.g. 10_000 (match jobs) or introduce cursor pagination. | +| **Prove** | Schema rejects high offset | + +### H4. Upload Content-Length admission (S8) + +| | | +| --- | --- | +| **Finding** | Missing length reserves max body bytes | +| **Owner** | OWNER:CODE | +| **Address** | Require Content-Length (411/400) or reserve after bounded read of actual size. | +| **Prove** | Length-less small uploads do not exhaust global budget | + +### H5. Authed summarize includes public docs (S9) + +| | | +| --- | --- | +| **Finding** | Stream summarize uses owner-only equality, excluding public corpus | +| **Owner** | OWNER:CODE | +| **Address** | Use `withOwnerReadScope` / public-OR semantics in `summarizeDocument` when ownerId present. | +| **Files** | `src/lib/rag.ts`, `src/app/api/answer/stream/route.ts`, summarize route | +| **Prove** | Authed summary of public UUID succeeds | + +### H6. Harden `/api/jobs` misconfig path (S11) + +| | | +| --- | --- | +| **Finding** | Missing server env returns demo jobs without auth | +| **Owner** | OWNER:CODE | +| **Address** | Authenticate first; on missing env return 503/`internal_error` outside explicit demo mode. | +| **Prove** | Unauthed + broken env → non-200 without demo payload | + +--- + +## Wave I — P3 backlog (schedule, don’t block launch) + +Address when touching the same files, or as a cleanup epic after Waves C–H. + +| ID | Issue | Address sketch | Prove | +| --- | --- | --- | --- | +| P3-R1 | Demotion floor `Math.max(hybrid_score, boosted)` | Clamp below hybrid only for explicit demotion delta; or subtract after floor; golden-eval gate any change | `eval:retrieval:quality` 36/36 | +| P3-R2 | D4 unknown blast radius | Docs + activation runbook: require governance enrichment % + golden eval before enabling | Operator checklist | +| P3-R3 | Governance-weighting principle wording | Reconcile process-hardening / ranking docs with demotion-only policy | Doc consistency review | +| P3-I1 | Bare `updateJob` without lease | Route stage updates through leased helper | Worker unit/integration | +| P3-I2 | Cleanup RPC JSONB vs typed column | Forward migration to typed compare | Schema tests + drift | +| P3-I3 | NULL `p_worker_id` lease bypass | Fail closed when worker id null for service callers; allowlist migrations | SQL test | +| P3-S1 | 10-min signed URLs | Product decision: shorten TTL or bind | Threat-model note | +| P3-S2 | Setup-status detail breadth | Gate on deep probe / admin role | Authed vs deep response shape | +| P3-S3 | Auth callback error reflection | Generic client message; server log detail | Callback harness | +| P3-U1 | Forced-colors glass/gloss tokens | Map to `Canvas` in `globals.css` | `verify:ui` a11y media | +| P3-U2 | Sheet `bg-black/45` | Use `--overlay-backdrop` | Visual parity | +| P3-U3 | `active:scale` without `motion-safe` | Prefix `motion-safe:` | Reduced-motion check | +| P3-U4 | Brand squares forced-color-adjust | `forced-color-adjust: none` on brand spans | Forced-colors screenshot | +| P3-C1 | Duplicate `test:e2e` aliases | Keep one; alias documented or remove | `package.json` | +| P3-C2 | Spend/price env missing from example | Document with defaults | Env parity script | +| P3-C3 | Orphan `reindex-eval-gate` location | Move beside driver after E3 | Import graph | +| P3-PERF | Hybrid RPC latency tail | Capacity/RPC workstream (existing RAG findings #25) — not a drive-by | p90 metrics | +| P3-SIZE | Large modules | Continue extractive decomp when editing `rag.ts` / dashboard | LOC ratchet optional | + +--- + +## Wave J — Release confidence close-out + +After Waves B–H land (or are deferred with dated waiver): + +1. `⏸ npm run check:supabase-project` +2. `⏸ npm run check:drift` (allowlist empty → any surprise is a stop) +3. `⏸ npm run check:production-readiness` (configured prod context) +4. `⏸ npm run eval:retrieval:quality` + `eval:quality -- --rag-only` +5. `⏸ npm run verify:release` +6. Update PIA / backlog / this plan’s status column (add a “Status” column locally as work completes) +7. Record outcomes in [`branch-review-ledger.md`](branch-review-ledger.md) + +--- + +## Priority matrix (quick view) + +| Priority | IDs | Launch impact | +| --- | --- | --- | +| Block real patients | B1, B2 | Legal + secret placement | +| Block next release claim | B3, B4, J | Evidence missing | +| Fix before wide anon traffic | C1 | DoS / cost | +| Fix before multi-tenant trust expansion | D1, D2, D5 | Disclosure / regression class | +| Fix before next reindex / recovery | E1–E3 | Index correctness | +| Fix before next UI merge wave | F1–F4, G1–G4 | Silent CI holes / a11y | +| Hygiene | H1–H6, F5–F6, Wave I | Contracts & debt | + +--- + +## Out of scope for this plan + +- Broad product redesign or RAG Phase-N architecture beyond the listed P3 ranking floor +- Forced dependency upgrades (`dependency` shortcut) +- Destructive branch cleanup +- Live mutations without explicit operator confirmation + +--- + +## Tracking + +Append progress to the review ledger when a wave completes, and flip matching rows in +[`operator-backlog.md`](operator-backlog.md). Prefer linking PRs back to issue IDs in this document +(`C1`, `E2`, …) in the PR body. diff --git a/docs/operator-backlog.md b/docs/operator-backlog.md index 50a50e206..98fdff33a 100644 --- a/docs/operator-backlog.md +++ b/docs/operator-backlog.md @@ -8,6 +8,8 @@ This exists so that launch-blocking state lives in the repo instead of chat memo `⏳ pending`, `🔎 verify` (may already be done — confirm before repeating), `✅ done`, `—` (n/a). Update the row (and its runbook) when an action lands. The sequenced flow with exact commands and approval gates is [launch-operator-runbook.md](launch-operator-runbook.md); this table is the index. +Code + ops remediation waves from the 2026-07-14 multi-skill audit live in +[audit-remediation-plan-2026-07-14.md](audit-remediation-plan-2026-07-14.md). > Status column is seeded from repo runbooks + session memory and **must be confirmed against live > state** before acting — do not treat a `🔎 verify` row as authoritative. From d9ceeaf9c8061aa83e71b1f79b36012b836b83e8 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 15 Jul 2026 01:06:49 +0800 Subject: [PATCH 2/8] fix(docs): address PR 673 review and Prettier CI Align scrub/prove steps, isolate staging query-hash secret, cover structural P3 findings, and use executable npm run eval commands. Co-authored-by: Cursor --- docs/audit-remediation-plan-2026-07-14.md | 460 +++++++++++----------- docs/branch-review-ledger.md | 3 + 2 files changed, 234 insertions(+), 229 deletions(-) diff --git a/docs/audit-remediation-plan-2026-07-14.md b/docs/audit-remediation-plan-2026-07-14.md index a6b49aaa4..487343b05 100644 --- a/docs/audit-remediation-plan-2026-07-14.md +++ b/docs/audit-remediation-plan-2026-07-14.md @@ -15,13 +15,13 @@ every finding: major blockers first, then P2 sub-issues, then P3 cleanup. **Legend** -| Tag | Meaning | -| --- | --- | -| **OWNER:CODE** | Implementable in-repo | -| **OWNER:OPS** | Railway / Supabase / GitHub / OpenAI dashboard or CLI | -| **OWNER:LEGAL** | Privacy officer / counsel | -| **⏸** | Provider or legal confirmation required | -| **Prove** | Smallest acceptance check | +| Tag | Meaning | +| --------------- | ----------------------------------------------------- | +| **OWNER:CODE** | Implementable in-repo | +| **OWNER:OPS** | Railway / Supabase / GitHub / OpenAI dashboard or CLI | +| **OWNER:LEGAL** | Privacy officer / counsel | +| **⏸** | Provider or legal confirmation required | +| **Prove** | Smallest acceptance check | --- @@ -44,12 +44,12 @@ Wave J Release confidence close-out OWNER:OPS ⏸ Suggested PR granularity (one theme per PR): -1. Catalog rate-limit + public DTO redaction -2. Ingestion recovery + commit fallback -3. CI scope + critical safety assert + flake hygiene -4. A11y (describedby / icons / Tab / live region) -5. JSON error taxonomy + admin rate limits -6. Env example / bundle budget / summarize public scope +1. Catalog rate-limit + public DTO redaction +2. Ingestion recovery + commit fallback +3. CI scope + critical safety assert + flake hygiene +4. A11y (describedby / icons / Tab / live region) +5. JSON error taxonomy + admin rate limits +6. Env example / bundle budget / summarize public scope 7. Operator backlog status reconciliation (docs-only) --- @@ -58,22 +58,22 @@ Suggested PR granularity (one theme per PR): ### A1. Reconcile operator backlog vs launch runbook -| | | -| --- | --- | -| **Finding** | M5 — backlog still `⏳` for drift-codify / prod deploy / worker while runbook says more is already live | -| **Owner** | OWNER:OPS (+ docs edit) | +| | | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | M5 — backlog still `⏳` for drift-codify / prod deploy / worker while runbook says more is already live | +| **Owner** | OWNER:OPS (+ docs edit) | | **Address** | Diff each backlog row against `launch-operator-runbook.md`, July-8 apply notes, empty `supabase/drift-allowlist.json`, and linked migration list. Flip rows to `✅` / `🔎 verify` / keep `⏳` with evidence date. | -| **Files** | `docs/operator-backlog.md`, optionally a short note in `docs/launch-operator-runbook.md` | -| **Prove** | `⏸ npx supabase migration list --linked`; `⏸ npm run check:drift`; human sign-off that backlog matches live | +| **Files** | `docs/operator-backlog.md`, optionally a short note in `docs/launch-operator-runbook.md` | +| **Prove** | `⏸ npx supabase migration list --linked`; `⏸ npm run check:drift`; human sign-off that backlog matches live | ### A2. Confirm July-13 scrub / lexical migration posture -| | | -| --- | --- | -| **Finding** | Process-hardening still lists July-13 PHI-scrub / lexical items as remaining in places | -| **Owner** | OWNER:OPS | -| **Address** | Confirm whether `20260713103000_scrub_legacy_rag_query_text` (and related) are applied; if not, schedule under Wave B; if yes, mark docs done. | -| **Prove** | `⏸` row counts `rag_queries` not like `redacted-query:%` → expect `0`; lexical probe from process-hardening | +| | | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | Process-hardening still lists July-13 PHI-scrub / lexical items as remaining in places | +| **Owner** | OWNER:OPS | +| **Address** | Confirm whether `20260713103000_scrub_legacy_rag_query_text` (and related) are applied; if not, schedule under Wave B; if yes, mark docs done. | +| **Prove** | `⏸` all four process-hardening scrub checks expect `0` (unless `RAG_PERSIST_RAW_QUERY_TEXT` is deliberately enabled): `select count(*) from rag_queries where query not like 'redacted-query:%';`; `select count(*) from rag_query_misses where query not like 'redacted-query:%' or normalized_query not like 'redacted-query:%';`; `select count(*) from rag_retrieval_logs where query not like 'redacted-query:%' or (normalized_query is not null and normalized_query not like 'redacted-query:%');`; `select count(*) from rag_response_cache where normalized_query not like 'redacted-cache:%';`; plus lexical probe from process-hardening | --- @@ -81,48 +81,48 @@ Suggested PR granularity (one theme per PR): ### B1. Close PIA-1 APP 8 / overseas processing basis -| | | -| --- | --- | -| **Finding** | M1 — Railway Singapore + OpenAI US process incidental PHI queries | -| **Owner** | OWNER:LEGAL + OWNER:OPS | +| | | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | M1 — Railway Singapore + OpenAI US process incidental PHI queries | +| **Owner** | OWNER:LEGAL + OWNER:OPS | | **Address** | Execute checklist in [`openai-cross-border-basis.md`](openai-cross-border-basis.md): OpenAI DPA, ZDR eligibility enablement where chosen, Railway DPA/processor record, update PIA status tables and `/privacy` copy only after counsel approval. | -| **Prove** | Status record rows move from `_no_` to dated `_yes_` / approved alternative; PIA-1 no longer High-open | +| **Prove** | Status record rows move from `_no_` to dated `_yes_` / approved alternative; PIA-1 no longer High-open | ### B2. Verify Railway `RAG_QUERY_HASH_SECRET` (PIA-2) -| | | -| --- | --- | -| **Finding** | M2 — secret verified in GitHub CI, Railway runtime still `🔎 verify` | -| **Owner** | OWNER:OPS | -| **Address** | Set/confirm same ≥16-char value in Railway runtime (prod + staging). Confirm boot smoke and deep readiness. | -| **Prove** | `⏸ npm run check:deployment-readiness` / production health boot; backlog → `✅` | +| | | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | M2 — secret verified in GitHub CI, Railway runtime still `🔎 verify` | +| **Owner** | OWNER:OPS | +| **Address** | Set/confirm a ≥16-char value in Railway **production** that matches the GitHub Actions secret used for CI smoke. For staging, set a **separate** staging-only `RAG_QUERY_HASH_SECRET` per [`staging-setup.md`](staging-setup.md) — do not reuse the production HMAC key. Confirm boot smoke and deep readiness. | +| **Prove** | `⏸ npm run check:deployment-readiness` / production health boot; backlog → `✅` | ### B3. Restore OpenAI quota and run release gates -| | | -| --- | --- | -| **Finding** | M3 — release gate + golden evals incomplete; prior quota exhaustion | -| **Owner** | OWNER:OPS | -| **Address** | Restore embedding/completions quota → run `eval:retrieval:quality` (36/36) → `eval:quality -- --rag-only` → `npm run verify:release` per [`launch-operator-runbook.md`](launch-operator-runbook.md) §2. | -| **Prove** | Paste summaries into release notes / backlog; canary path in B4 | +| | | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | M3 — release gate + golden evals incomplete; prior quota exhaustion | +| **Owner** | OWNER:OPS | +| **Address** | Restore embedding/completions quota → run `npm run eval:retrieval:quality` (36/36) → `npm run eval:quality -- --rag-only` → `npm run verify:release` per [`launch-operator-runbook.md`](launch-operator-runbook.md) §2. | +| **Prove** | Paste summaries into release notes / backlog; canary path in B4 | ### B4. Eval Canary trust + staging soak -| | | -| --- | --- | -| **Finding** | M3 leftovers — canary not yet “two greens”; staging soak pending | -| **Owner** | OWNER:OPS | +| | | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | M3 leftovers — canary not yet “two greens”; staging soak pending | +| **Owner** | OWNER:OPS | | **Address** | Provision staging if absent ([`staging-setup.md`](staging-setup.md)); soak (`scripts/soak-test.ts --confirm-staging`, answer p95 ≤ 25 s); run two consecutive Eval Canary greens from `main`. | -| **Prove** | Soak log + two green workflow runs recorded in backlog | +| **Prove** | Soak log + two green workflow runs recorded in backlog | ### B5. Worker image / secret / seed post-deploy confirm -| | | -| --- | --- | -| **Finding** | Worker redeploy, registry seed, `HEALTH_DEEP_PROBE_SECRET`, auth connection cap | -| **Owner** | OWNER:OPS | +| | | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | Worker redeploy, registry seed, `HEALTH_DEEP_PROBE_SECRET`, auth connection cap | +| **Owner** | OWNER:OPS | | **Address** | Follow runbook §6 after B3: `reindex:health`, seed registry/differentials, wire ops-digest secrets if desired, flip auth connection allocation **before** vertical scale ([`auth-connection-cap-runbook.md`](auth-connection-cap-runbook.md)). | -| **Prove** | Non-empty Services/Forms; reindex health clear; optional ops-digest cron enabled | +| **Prove** | Non-empty Services/Forms; reindex health clear; optional ops-digest cron enabled | --- @@ -130,13 +130,13 @@ Suggested PR granularity (one theme per PR): ### C1. Rate-limit anonymous public catalogs (M4) -| | | -| --- | --- | -| **Finding** | Catalog routes short-circuit before rate limits when no auth signal | -| **Owner** | OWNER:CODE | +| | | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | Catalog routes short-circuit before rate limits when no auth signal | +| **Owner** | OWNER:CODE | | **Address** | Always resolve a rate-limit subject (anonymous IP/fingerprint) for medications/registry/differentials catalog GETs; or force lightweight `fields=index` as default for anon and hard-cap full payloads. Prefer both: limiter + default index shape. | -| **Files** | `src/lib/public-api-access.ts`, `src/app/api/medications/route.ts`, `src/app/api/registry/**`, `src/app/api/differentials/**`, rate-limit tests | -| **Prove** | Unit/route tests: unauth GET still calls limiter; optional size cap; `npm run verify:cheap` | +| **Files** | `src/lib/public-api-access.ts`, `src/app/api/medications/route.ts`, `src/app/api/registry/**`, `src/app/api/differentials/**`, rate-limit tests | +| **Prove** | Unit/route tests: unauth GET still calls limiter; optional size cap; `npm run verify:cheap` | --- @@ -144,52 +144,52 @@ Suggested PR granularity (one theme per PR): ### D1. Redact internal fields on authenticated public-document DTOs (S1 / S10 detail) -| | | -| --- | --- | -| **Finding** | Authed viewers of `owner_id IS NULL` docs receive `storage_path`, `content_hash`, etc. | -| **Owner** | OWNER:CODE | +| | | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | Authed viewers of `owner_id IS NULL` docs receive `storage_path`, `content_hash`, etc. | +| **Owner** | OWNER:CODE | | **Address** | Apply the same omit-list used for anonymous responses whenever `document.owner_id !== access.ownerId` (or whenever public overlay rows are returned). Keep full fields for the owning user / operators only if product requires. | -| **Files** | `src/app/api/documents/route.ts`, `src/app/api/documents/[id]/route.ts`, focused API tests | -| **Prove** | Authed non-owner GET public doc omits `storage_path` / `content_hash` / `import_batch_id` | +| **Files** | `src/app/api/documents/route.ts`, `src/app/api/documents/[id]/route.ts`, focused API tests | +| **Prove** | Authed non-owner GET public doc omits `storage_path` / `content_hash` / `import_batch_id` | ### D2. Defense-in-depth tenancy CI guard (M6) -| | | -| --- | --- | -| **Finding** | Service-role single layer; forgotten owner filter is a full leak class | -| **Owner** | OWNER:CODE (+ OWNER:OPS for live A/B) | +| | | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | Service-role single layer; forgotten owner filter is a full leak class | +| **Owner** | OWNER:CODE (+ OWNER:OPS for live A/B) | | **Address** | Add static CI guard grepping `src/app/api/**` for admin queries on owner-scoped tables without known helpers (`withOwnerReadScope`, `requireOwnerScope`, `.eq("owner_id"` patterns allowlisted). Document remaining intentional exceptions. Schedule `⏸` user A vs B smoke on documents + signed URLs + search. | -| **Files** | new `scripts/check-owner-scope-api.mjs` (or extend existing), `package.json` / `verify:cheap`, tests; update `docs/tenancy-defense-in-depth-review.md` | -| **Prove** | Guard fails on a synthetic unscope fixture; live A/B green | +| **Files** | new `scripts/check-owner-scope-api.mjs` (or extend existing), `package.json` / `verify:cheap`, tests; update `docs/tenancy-defense-in-depth-review.md` | +| **Prove** | Guard fails on a synthetic unscope fixture; live A/B green | ### D3. Auth UI: prefer validated user for privilege display (S2) -| | | -| --- | --- | -| **Finding** | Browser `getSession()` can show signed-in until API 401 | -| **Owner** | OWNER:CODE | +| | | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | Browser `getSession()` can show signed-in until API 401 | +| **Owner** | OWNER:CODE | | **Address** | Initialize `AuthProvider` via `getUser()` (or revalidate session with `getUser` before elevating UI), keep optimistic cache only for non-privileged chrome. | -| **Files** | `src/lib/supabase/client.tsx`, auth UI tests | -| **Prove** | Tampered/stale local session → UI not elevated / recovers to signed-out | +| **Files** | `src/lib/supabase/client.tsx`, auth UI tests | +| **Prove** | Tampered/stale local session → UI not elevated / recovers to signed-out | ### D4. Public-upload pool principal hygiene (S3) -| | | -| --- | --- | -| **Finding** | Shared quarantine owner is high blast radius when uploads enabled | -| **Owner** | OWNER:OPS (+ CODE docs) | +| | | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Finding** | Shared quarantine owner is high blast radius when uploads enabled | +| **Owner** | OWNER:OPS (+ CODE docs) | | **Address** | Ensure pool UUID has no human password/OAuth sessions; document as non-interactive principal; keep `NEXT_PUBLIC_PUBLIC_UPLOADS_ENABLED` off in prod unless moderation workflow is staffed. | -| **Prove** | Auth user list check + prod env flag audit | +| **Prove** | Auth user list check + prod env flag audit | ### D5. Bulk / internal error redaction (S10) -| | | -| --- | --- | -| **Finding** | Bulk edit returns raw PostgREST/`error.message` | -| **Owner** | OWNER:CODE | +| | | +| ----------- | ------------------------------------------------------------- | +| **Finding** | Bulk edit returns raw PostgREST/`error.message` | +| **Owner** | OWNER:CODE | | **Address** | Map to `PublicApiError` / stable codes; log server-side only. | -| **Files** | `src/app/api/documents/bulk/route.ts` | -| **Prove** | Injected update failure → no raw Postgres text in JSON | +| **Files** | `src/app/api/documents/bulk/route.ts` | +| **Prove** | Injected update failure → no raw Postgres text in JSON | --- @@ -197,33 +197,33 @@ Suggested PR granularity (one theme per PR): ### E1. Fix unreachable commit fallback (I1) -| | | -| --- | --- | -| **Finding** | `throw` before `upsertIndexQuality` / `deleteStaleIndexGenerationRows` | -| **Owner** | OWNER:CODE | +| | | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | `throw` before `upsertIndexQuality` / `deleteStaleIndexGenerationRows` | +| **Owner** | OWNER:CODE | | **Address** | Restructure `commitDocumentIndexGeneration`: on RPC error, if error is not `lease_lost`, run the documented client fallback then rethrow or return structured failure; never leave unreachable statements. Enable unreachable-code lint/`allowUnreachableCode: false` for worker if feasible. | -| **Files** | `worker/main.ts`, worker/unit tests if present | -| **Prove** | Simulated commit RPC failure invokes cleanup once; typecheck/lint clean | +| **Files** | `worker/main.ts`, worker/unit tests if present | +| **Prove** | Simulated commit RPC failure invokes cleanup once; typecheck/lint clean | ### E2. Deduplicate recovery retries per document (I2) -| | | -| --- | --- | -| **Finding** | `pending` + `failed` → two retry actions → unique index violation | -| **Owner** | OWNER:CODE | +| | | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | `pending` + `failed` → two retry actions → unique index violation | +| **Owner** | OWNER:CODE | | **Address** | In `buildIngestionRecoveryPlan`, emit at most one retry per `document_id` (prefer existing `pending`, else promote one `failed`). Align `recover-ingestion-queue.ts` with `reindex.ts` status query policy. | -| **Files** | `src/lib/ingestion-recovery.ts`, `scripts/recover-ingestion-queue.ts`, `tests/ingestion-recovery.test.ts` | -| **Prove** | New unit test: pending+failed → `retryCount === 1`; script dry-run does not throw `23505` | +| **Files** | `src/lib/ingestion-recovery.ts`, `scripts/recover-ingestion-queue.ts`, `tests/ingestion-recovery.test.ts` | +| **Prove** | New unit test: pending+failed → `retryCount === 1`; script dry-run does not throw `23505` | ### E3. Wire reindex eval gate (I3) -| | | -| --- | --- | -| **Finding** | `decideReindexGate` unused by `scripts/reindex.ts` | -| **Owner** | OWNER:CODE | +| | | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | `decideReindexGate` unused by `scripts/reindex.ts` | +| **Owner** | OWNER:CODE | | **Address** | After worker settles each generation (or at script end), load eval summary JSON and call `decideReindexGate`; exit non-zero on `NO_GO`. Keep opt-out flag for emergency ops (`--skip-eval-gate`) documented and logged. Optionally relocate module next to the driver under `scripts/`. | -| **Files** | `scripts/reindex.ts`, `src/lib/reindex-eval-gate.ts`, docs in reindex runbook | -| **Prove** | Fixture NO_GO summary → non-zero exit; GO passes | +| **Files** | `scripts/reindex.ts`, `src/lib/reindex-eval-gate.ts`, docs in reindex runbook | +| **Prove** | Fixture NO_GO summary → non-zero exit; GO passes | --- @@ -231,58 +231,58 @@ Suggested PR granularity (one theme per PR): ### F1. Expand CI UI scope patterns (C3) -| | | -| --- | --- | -| **Finding** | `src/lib/app-modes.ts`, `ui-copy.ts`, document-flow / search-navigation helpers do not set `ui_changed` | -| **Owner** | OWNER:CODE | +| | | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | `src/lib/app-modes.ts`, `ui-copy.ts`, document-flow / search-navigation helpers do not set `ui_changed` | +| **Owner** | OWNER:CODE | | **Address** | Extend `uiPatterns` in `scripts/ci-change-scope.mjs` to include those libs (and any other shell-imported copy/routing modules). Add self-tests. | -| **Prove** | `node scripts/ci-change-scope.mjs --json --files src/lib/app-modes.ts` → `ui_changed: true` | +| **Prove** | `node scripts/ci-change-scope.mjs --json --files src/lib/app-modes.ts` → `ui_changed: true` | ### F2. Expand CI RAG-eval scope patterns (C4) -| | | -| --- | --- | -| **Finding** | `clinical-safety.ts`, `privacy.ts`, `public-api-access.ts`, `private-search-scope.ts` miss `rag_eval` | -| **Owner** | OWNER:CODE | +| | | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | `clinical-safety.ts`, `privacy.ts`, `public-api-access.ts`, `private-search-scope.ts` miss `rag_eval` | +| **Owner** | OWNER:CODE | | **Address** | Add those modules to `ragEvalPatterns`; keep test pattern coverage; add self-tests. Fix stale “advisory regression” sentence in `docs/process-hardening.md` L11. | -| **Prove** | Classify `src/lib/clinical-safety.ts` → `rag_eval_changed: true` | +| **Prove** | Classify `src/lib/clinical-safety.ts` → `rag_eval_changed: true` | ### F3. Harden `@critical` safety UI assertion (C6) -| | | -| --- | --- | -| **Finding** | Clozapine demo smoke treats safety findings as optional | -| **Owner** | OWNER:CODE | +| | | +| ----------- | ------------------------------------------------------------------------------------------------------- | +| **Finding** | Clozapine demo smoke treats safety findings as optional | +| **Owner** | OWNER:CODE | | **Address** | For that fixture, require `answer-safety-findings-trigger` and ≥1 `safety-finding-row` unconditionally. | -| **Files** | `tests/ui-smoke.spec.ts` | -| **Prove** | Focused Chromium critical title; intentional findings removal fails the test | +| **Files** | `tests/ui-smoke.spec.ts` | +| **Prove** | Focused Chromium critical title; intentional findings removal fails the test | ### F4. Flake ledger → quarantine or fix (C5 / C7) -| | | -| --- | --- | -| **Finding** | Four known flakes in blocking suite; `@quarantine` unused; sleeps/`networkidle` swallows | -| **Owner** | OWNER:CODE | +| | | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | Four known flakes in blocking suite; `@quarantine` unused; sleeps/`networkidle` swallows | +| **Owner** | OWNER:CODE | | **Address** | For each `flake-ledger.json` entry: fix root cause **or** tag `@quarantine` with owner/date. Replace `waitForTimeout` with condition waits; stop relying on swallowed `networkidle` for readiness. | -| **Prove** | Either ledger empty or every ledger title tagged/fixed; critical/regression green | +| **Prove** | Either ledger empty or every ledger title tagged/fixed; critical/regression green | ### F5. Align `.env.example` with safe defaults (C1) -| | | -| --- | --- | -| **Finding** | Example enables `RAG_TEXT_WEAK_OR_RELAXATION=true` | -| **Owner** | OWNER:CODE | +| | | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | Example enables `RAG_TEXT_WEAK_OR_RELAXATION=true` | +| **Owner** | OWNER:CODE | | **Address** | Set example to `false` or comment as opt-in experiment requiring golden eval. Document spend/price env vars (P3) in the same pass if touching the file. | -| **Prove** | Example matches `env.ts` defaults for risky flags | +| **Prove** | Example matches `env.ts` defaults for risky flags | ### F6. Capture and enforce bundle budget (C2) -| | | -| --- | --- | -| **Finding** | `enforce: false`, no baseline | -| **Owner** | OWNER:CODE | +| | | +| ----------- | --------------------------------------------------------------------------------------------------------- | +| **Finding** | `enforce: false`, no baseline | +| **Owner** | OWNER:CODE | | **Address** | On a known-good `npm run build`, run `npm run check:bundle-budget -- --update`, then set `enforce: true`. | -| **Prove** | CI fails on synthetic budget break; passes on baseline | +| **Prove** | CI fails on synthetic budget break; passes on baseline | --- @@ -290,41 +290,41 @@ Suggested PR granularity (one theme per PR): ### G1. Fix dead `aria-describedby` (U1) -| | | -| --- | --- | -| **Finding** | Description span is `aria-hidden="true"` while referenced by `aria-describedby` | -| **Owner** | OWNER:CODE | -| **Address** | Remove `aria-hidden` from description span in `mode-action-popup.tsx` (keep description out of the accessible *name*). | -| **Prove** | Chromium a11y check / axe on action menu; unit DOM assertion | +| | | +| ----------- | ---------------------------------------------------------------------------------------------------------------------- | +| **Finding** | Description span is `aria-hidden="true"` while referenced by `aria-describedby` | +| **Owner** | OWNER:CODE | +| **Address** | Remove `aria-hidden` from description span in `mode-action-popup.tsx` (keep description out of the accessible _name_). | +| **Prove** | Chromium a11y check / axe on action menu; unit DOM assertion | ### G2. Dynamic Lucide icons `aria-hidden` (U2) -| | | -| --- | --- | -| **Finding** | ~25 variable `` sites; ESLint rule gap | -| **Owner** | OWNER:CODE | +| | | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | ~25 variable `` sites; ESLint rule gap | +| **Owner** | OWNER:CODE | | **Address** | Add `aria-hidden="true"` at all decorative dynamic icon sites; extend `eslint-rules/require-lucide-icon-aria.mjs` to track LucideIcon variables when feasible. | -| **Prove** | Lint clean; spot-check mode menu / send button | +| **Prove** | Lint clean; spot-check mode menu / send button | ### G3. Close mode menu on Tab (U3) -| | | -| --- | --- | -| **Finding** | Tab leaves `role="menu"` open | -| **Owner** | OWNER:CODE | +| | | +| ----------- | -------------------------------------------------------------------------------------------- | +| **Finding** | Tab leaves `role="menu"` open | +| **Owner** | OWNER:CODE | | **Address** | In `handleModeOptionKeyDown`, on Tab: `setModeMenuOpen(false)` and allow default focus move. | -| **Files** | `master-search-header.tsx` | -| **Prove** | Keyboard Playwright: open menu → Tab → menu gone | +| **Files** | `master-search-header.tsx` | +| **Prove** | Keyboard Playwright: open menu → Tab → menu gone | ### G4. Announce answer readiness (U4) -| | | -| --- | --- | -| **Finding** | No live region for streaming answer completion | -| **Owner** | OWNER:CODE | +| | | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | No live region for streaming answer completion | +| **Owner** | OWNER:CODE | | **Address** | Keep/extend `role="status" aria-live="polite"` through loading → complete; announce start once and “Answer ready” (or equivalent) on completion — not per token. | -| **Files** | `answer-status.tsx` / `ClinicalDashboard.tsx` / `answer-content.tsx` | -| **Prove** | Accessibility test asserts status text transitions | +| **Files** | `answer-status.tsx` / `ClinicalDashboard.tsx` / `answer-content.tsx` | +| **Prove** | Accessibility test asserts status text transitions | --- @@ -332,58 +332,58 @@ Suggested PR granularity (one theme per PR): ### H1. Unify JSON error envelopes (S4 / S5) -| | | -| --- | --- | -| **Finding** | Auth and many routes return `{ error }` without `code`/`message` | -| **Owner** | OWNER:CODE | +| | | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | Auth and many routes return `{ error }` without `code`/`message` | +| **Owner** | OWNER:CODE | | **Address** | Route all failures through `jsonError` / `PublicApiError`. Update auth helper first, then upload/document/feedback/demo paths in follow-up commits if too large. | -| **Prove** | Contract tests for 401/400/404 sample paths | +| **Prove** | Contract tests for 401/400/404 sample paths | ### H2. Rate-limit authenticated write/admin routes (S6) -| | | -| --- | --- | -| **Finding** | bulk / labels / table-facts / ingestion quality / eval-cases / jobs lack buckets | -| **Owner** | OWNER:CODE | +| | | +| ----------- | --------------------------------------------------------------------------------------------------------------- | +| **Finding** | bulk / labels / table-facts / ingestion quality / eval-cases / jobs lack buckets | +| **Owner** | OWNER:CODE | | **Address** | Assign buckets (reuse `document_read` or add `document_admin` / `ingestion_admin`); return 429 + `Retry-After`. | -| **Prove** | Mocked exhaustion → 429 | +| **Prove** | Mocked exhaustion → 429 | ### H3. Cap document list offset (S7) -| | | -| --- | --- | -| **Finding** | Offset max 1_000_000 | -| **Owner** | OWNER:CODE | +| | | +| ----------- | --------------------------------------------------------------- | +| **Finding** | Offset max 1_000_000 | +| **Owner** | OWNER:CODE | | **Address** | Cap to e.g. 10_000 (match jobs) or introduce cursor pagination. | -| **Prove** | Schema rejects high offset | +| **Prove** | Schema rejects high offset | ### H4. Upload Content-Length admission (S8) -| | | -| --- | --- | -| **Finding** | Missing length reserves max body bytes | -| **Owner** | OWNER:CODE | +| | | +| ----------- | ------------------------------------------------------------------------------ | +| **Finding** | Missing length reserves max body bytes | +| **Owner** | OWNER:CODE | | **Address** | Require Content-Length (411/400) or reserve after bounded read of actual size. | -| **Prove** | Length-less small uploads do not exhaust global budget | +| **Prove** | Length-less small uploads do not exhaust global budget | ### H5. Authed summarize includes public docs (S9) -| | | -| --- | --- | -| **Finding** | Stream summarize uses owner-only equality, excluding public corpus | -| **Owner** | OWNER:CODE | +| | | +| ----------- | ------------------------------------------------------------------------------------------- | +| **Finding** | Stream summarize uses owner-only equality, excluding public corpus | +| **Owner** | OWNER:CODE | | **Address** | Use `withOwnerReadScope` / public-OR semantics in `summarizeDocument` when ownerId present. | -| **Files** | `src/lib/rag.ts`, `src/app/api/answer/stream/route.ts`, summarize route | -| **Prove** | Authed summary of public UUID succeeds | +| **Files** | `src/lib/rag.ts`, `src/app/api/answer/stream/route.ts`, summarize route | +| **Prove** | Authed summary of public UUID succeeds | ### H6. Harden `/api/jobs` misconfig path (S11) -| | | -| --- | --- | -| **Finding** | Missing server env returns demo jobs without auth | -| **Owner** | OWNER:CODE | +| | | +| ----------- | ------------------------------------------------------------------------------------------ | +| **Finding** | Missing server env returns demo jobs without auth | +| **Owner** | OWNER:CODE | | **Address** | Authenticate first; on missing env return 503/`internal_error` outside explicit demo mode. | -| **Prove** | Unauthed + broken env → non-200 without demo payload | +| **Prove** | Unauthed + broken env → non-200 without demo payload | --- @@ -391,26 +391,28 @@ Suggested PR granularity (one theme per PR): Address when touching the same files, or as a cleanup epic after Waves C–H. -| ID | Issue | Address sketch | Prove | -| --- | --- | --- | --- | -| P3-R1 | Demotion floor `Math.max(hybrid_score, boosted)` | Clamp below hybrid only for explicit demotion delta; or subtract after floor; golden-eval gate any change | `eval:retrieval:quality` 36/36 | -| P3-R2 | D4 unknown blast radius | Docs + activation runbook: require governance enrichment % + golden eval before enabling | Operator checklist | -| P3-R3 | Governance-weighting principle wording | Reconcile process-hardening / ranking docs with demotion-only policy | Doc consistency review | -| P3-I1 | Bare `updateJob` without lease | Route stage updates through leased helper | Worker unit/integration | -| P3-I2 | Cleanup RPC JSONB vs typed column | Forward migration to typed compare | Schema tests + drift | -| P3-I3 | NULL `p_worker_id` lease bypass | Fail closed when worker id null for service callers; allowlist migrations | SQL test | -| P3-S1 | 10-min signed URLs | Product decision: shorten TTL or bind | Threat-model note | -| P3-S2 | Setup-status detail breadth | Gate on deep probe / admin role | Authed vs deep response shape | -| P3-S3 | Auth callback error reflection | Generic client message; server log detail | Callback harness | -| P3-U1 | Forced-colors glass/gloss tokens | Map to `Canvas` in `globals.css` | `verify:ui` a11y media | -| P3-U2 | Sheet `bg-black/45` | Use `--overlay-backdrop` | Visual parity | -| P3-U3 | `active:scale` without `motion-safe` | Prefix `motion-safe:` | Reduced-motion check | -| P3-U4 | Brand squares forced-color-adjust | `forced-color-adjust: none` on brand spans | Forced-colors screenshot | -| P3-C1 | Duplicate `test:e2e` aliases | Keep one; alias documented or remove | `package.json` | -| P3-C2 | Spend/price env missing from example | Document with defaults | Env parity script | -| P3-C3 | Orphan `reindex-eval-gate` location | Move beside driver after E3 | Import graph | -| P3-PERF | Hybrid RPC latency tail | Capacity/RPC workstream (existing RAG findings #25) — not a drive-by | p90 metrics | -| P3-SIZE | Large modules | Continue extractive decomp when editing `rag.ts` / dashboard | LOC ratchet optional | +| ID | Issue | Address sketch | Prove | +| ---------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| P3-R1 | Demotion floor `Math.max(hybrid_score, boosted)` | Clamp below hybrid only for explicit demotion delta; or subtract after floor; golden-eval gate any change | `npm run eval:retrieval:quality` 36/36 | +| P3-R2 | D4 unknown blast radius | Docs + activation runbook: require governance enrichment % + golden eval before enabling | Operator checklist | +| P3-R3 | Governance-weighting principle wording | Reconcile process-hardening / ranking docs with demotion-only policy | Doc consistency review | +| P3-I1 | Bare `updateJob` without lease | Route stage updates through leased helper | Worker unit/integration | +| P3-I2 | Cleanup RPC JSONB vs typed column | Forward migration to typed compare | Schema tests + drift | +| P3-I3 | NULL `p_worker_id` lease bypass | Fail closed when worker id null for service callers; allowlist migrations | SQL test | +| P3-S1 | 10-min signed URLs | Product decision: shorten TTL or bind | Threat-model note | +| P3-S2 | Setup-status detail breadth | Gate on deep probe / admin role | Authed vs deep response shape | +| P3-S3 | Auth callback error reflection | Generic client message; server log detail | Callback harness | +| P3-U1 | Forced-colors glass/gloss tokens | Map to `Canvas` in `globals.css` | `verify:ui` a11y media | +| P3-U2 | Sheet `bg-black/45` | Use `--overlay-backdrop` | Visual parity | +| P3-U3 | `active:scale` without `motion-safe` | Prefix `motion-safe:` | Reduced-motion check | +| P3-U4 | Brand squares forced-color-adjust | `forced-color-adjust: none` on brand spans | Forced-colors screenshot | +| P3-C1 | Duplicate `test:e2e` aliases | Keep one; alias documented or remove | `package.json` | +| P3-C2 | Spend/price env missing from example | Document with defaults | Env parity script | +| P3-C3 | Orphan `reindex-eval-gate` location | Move beside driver after E3 | Import graph | +| P3-STRUCT1 | `client-env.ts` duplicates `isLocalNoAuthMode` / `publicUploadsEnabled` from `env.ts` via raw `process.env` | Keep intentional client/server split but share one normalized helper or document the allowed divergence; add a parity regression if values can drift | `npm run check:env-parity`; focused env unit that fails if client/server helpers disagree | +| P3-STRUCT2 | `mockup`-named component files retained under `src/components/` | Keep while gated by `mockupsEnabled()`, or rename/remove once production call sites no longer need the mockup path; record disposition in ledger | Import/grep shows every production import goes through `mockupsEnabled()` or files are renamed/removed | +| P3-PERF | Hybrid RPC latency tail | Capacity/RPC workstream (existing RAG findings #25) — not a drive-by | p90 metrics | +| P3-SIZE | Large modules | Continue extractive decomp when editing `rag.ts` / dashboard | LOC ratchet optional | --- @@ -418,36 +420,36 @@ Address when touching the same files, or as a cleanup epic after Waves C–H. After Waves B–H land (or are deferred with dated waiver): -1. `⏸ npm run check:supabase-project` -2. `⏸ npm run check:drift` (allowlist empty → any surprise is a stop) -3. `⏸ npm run check:production-readiness` (configured prod context) -4. `⏸ npm run eval:retrieval:quality` + `eval:quality -- --rag-only` -5. `⏸ npm run verify:release` -6. Update PIA / backlog / this plan’s status column (add a “Status” column locally as work completes) +1. `⏸ npm run check:supabase-project` +2. `⏸ npm run check:drift` (allowlist empty → any surprise is a stop) +3. `⏸ npm run check:production-readiness` (configured prod context) +4. `⏸ npm run eval:retrieval:quality` + `npm run eval:quality -- --rag-only` +5. `⏸ npm run verify:release` +6. Update PIA / backlog / this plan’s status column (add a “Status” column locally as work completes) 7. Record outcomes in [`branch-review-ledger.md`](branch-review-ledger.md) --- ## Priority matrix (quick view) -| Priority | IDs | Launch impact | -| --- | --- | --- | -| Block real patients | B1, B2 | Legal + secret placement | -| Block next release claim | B3, B4, J | Evidence missing | -| Fix before wide anon traffic | C1 | DoS / cost | -| Fix before multi-tenant trust expansion | D1, D2, D5 | Disclosure / regression class | -| Fix before next reindex / recovery | E1–E3 | Index correctness | -| Fix before next UI merge wave | F1–F4, G1–G4 | Silent CI holes / a11y | -| Hygiene | H1–H6, F5–F6, Wave I | Contracts & debt | +| Priority | IDs | Launch impact | +| --------------------------------------- | -------------------- | ----------------------------- | +| Block real patients | B1, B2 | Legal + secret placement | +| Block next release claim | B3, B4, J | Evidence missing | +| Fix before wide anon traffic | C1 | DoS / cost | +| Fix before multi-tenant trust expansion | D1, D2, D5 | Disclosure / regression class | +| Fix before next reindex / recovery | E1–E3 | Index correctness | +| Fix before next UI merge wave | F1–F4, G1–G4 | Silent CI holes / a11y | +| Hygiene | H1–H6, F5–F6, Wave I | Contracts & debt | --- ## Out of scope for this plan -- Broad product redesign or RAG Phase-N architecture beyond the listed P3 ranking floor -- Forced dependency upgrades (`dependency` shortcut) -- Destructive branch cleanup -- Live mutations without explicit operator confirmation +- Broad product redesign or RAG Phase-N architecture beyond the listed P3 ranking floor +- Forced dependency upgrades (`dependency` shortcut) +- Destructive branch cleanup +- Live mutations without explicit operator confirmation --- diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 99a081e71..44716dfa1 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -490,3 +490,6 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD | 2026-07-14 | PR #655 / codex/release-blocker-remediation | 3ed3a7a2df37d7d15143ab7606e5748ac7ecca09 + reviewed follow-up diff | offline dose-route latency follow-up | The next isolated timeout was a short IM/PO agitation question receiving the same blanket ten-term AND expansion. Agitation dose/route retrieval now keeps only the dose and route signals present in the question; the exact case retrieves its expected source and five citations in 1.54 seconds without a model. All remaining RAG cases 23–44 passed individually, so no further deadline crash remains. | Focused clinical-search/retrieval Vitest 111/111; scoped ESLint; Prettier; `git diff --check`; live provider-free case 22 passed; live provider-free cases 23–44 passed individually. | | 2026-07-14 | PR #655 / codex/release-blocker-remediation | a3f3a89676015cd5f018c07e8c3ad9483f91cef6 + reviewed follow-up diff | offline adversarial-latency follow-up | The final blocking offline-quality failure was an adversarial secret-exfiltration query that correctly refused but first spent about 25 seconds in lexical retrieval. Adversarial manipulation now short-circuits at the search boundary before provider-client creation, cache access, classification, aliases, or Supabase work, and is never cached. | Focused Vitest 2/2; scoped ESLint; Prettier; full TypeScript; `git diff --check`; live provider-free adversarial case completed in 100 ms with 0 ms RPC time; `eval:quality:release:offline` passed with zero blocking failures and zero model, request-ID, token, cost, or generation-latency evidence. Flaky local browser/composite suites intentionally not repeated; hosted CI remains authoritative. | | 2026-07-14 | PR #666 / codex/release-blocker-remediation | 9b56eebe4b23ab783207445fb827c317c8d59be8 + reviewed follow-up diff | review-followup | One late P2 retrieval-contract gap was confirmed: the optimized agitation query retained IM/PO but could drop other already-supported amount, route, and frequency aliases. Medication evidence intent is now shared with retrieval selection, and focused agitation queries preserve requested numeric units, SC, SL, PRN, and frequency signals without restoring the broad ten-term expansion. | GitHub review-thread inspection; focused clinical-search/retrieval Vitest 112/112; scoped ESLint; Prettier; `git diff --check`. Hosted final-head TypeScript/build/CI and exact-head staging evidence remain required after push. | +| 2026-07-15 | PR #673 / cursor/audit-remediation-plan-0411 | d061beec41a906f5a3be761e719be0ecbd9279ea + review-followup | docs audit remediation plan review + CI | Hosted static-pr failed on Prettier for the new plan. Codex P1s: A2 scrub prove covered only +ag_queries (now all four process-hardening tables); B2 staging shared prod RAG_QUERY_HASH_SECRET (now staging-only). CodeRabbit: format, Wave I coverage for client-env.ts / mockup-named components (P3-STRUCT1/2), and +pm run prefixes for eval commands. | Focused Prettier on changed docs; git diff --check. Docs-only; no app code or provider checks. | From e7a9e40688349fd840db147b5e943f6140a88a3a Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:43:18 +0000 Subject: [PATCH 3/8] fix: apply CodeRabbit auto-fixes Fixed 1 file(s) based on 2 unresolved review comments. Co-authored-by: CodeRabbit --- docs/branch-review-ledger.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 44716dfa1..8d79300ee 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -492,4 +492,4 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD | 2026-07-14 | PR #666 / codex/release-blocker-remediation | 9b56eebe4b23ab783207445fb827c317c8d59be8 + reviewed follow-up diff | review-followup | One late P2 retrieval-contract gap was confirmed: the optimized agitation query retained IM/PO but could drop other already-supported amount, route, and frequency aliases. Medication evidence intent is now shared with retrieval selection, and focused agitation queries preserve requested numeric units, SC, SL, PRN, and frequency signals without restoring the broad ten-term expansion. | GitHub review-thread inspection; focused clinical-search/retrieval Vitest 112/112; scoped ESLint; Prettier; `git diff --check`. Hosted final-head TypeScript/build/CI and exact-head staging evidence remain required after push. | | 2026-07-15 | PR #673 / cursor/audit-remediation-plan-0411 | d061beec41a906f5a3be761e719be0ecbd9279ea + review-followup | docs audit remediation plan review + CI | Hosted static-pr failed on Prettier for the new plan. Codex P1s: A2 scrub prove covered only ag_queries (now all four process-hardening tables); B2 staging shared prod RAG_QUERY_HASH_SECRET (now staging-only). CodeRabbit: format, Wave I coverage for client-env.ts / mockup-named components (P3-STRUCT1/2), and -pm run prefixes for eval commands. | Focused Prettier on changed docs; git diff --check. Docs-only; no app code or provider checks. | +npm run prefixes for eval commands. | Focused Prettier on changed docs; git diff --check. Docs-only; no app code or provider checks. | From e54a3d44f0ec138d31f886e2f17ad03a963fc0fc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Jul 2026 18:29:57 +0000 Subject: [PATCH 4/8] fix: resolve merge conflict in branch-review-ledger.md --- docs/branch-review-ledger.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 56d8016af..0077c7012 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -490,10 +490,7 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD | 2026-07-14 | PR #655 / codex/release-blocker-remediation | 3ed3a7a2df37d7d15143ab7606e5748ac7ecca09 + reviewed follow-up diff | offline dose-route latency follow-up | The next isolated timeout was a short IM/PO agitation question receiving the same blanket ten-term AND expansion. Agitation dose/route retrieval now keeps only the dose and route signals present in the question; the exact case retrieves its expected source and five citations in 1.54 seconds without a model. All remaining RAG cases 23–44 passed individually, so no further deadline crash remains. | Focused clinical-search/retrieval Vitest 111/111; scoped ESLint; Prettier; `git diff --check`; live provider-free case 22 passed; live provider-free cases 23–44 passed individually. | | 2026-07-14 | PR #655 / codex/release-blocker-remediation | a3f3a89676015cd5f018c07e8c3ad9483f91cef6 + reviewed follow-up diff | offline adversarial-latency follow-up | The final blocking offline-quality failure was an adversarial secret-exfiltration query that correctly refused but first spent about 25 seconds in lexical retrieval. Adversarial manipulation now short-circuits at the search boundary before provider-client creation, cache access, classification, aliases, or Supabase work, and is never cached. | Focused Vitest 2/2; scoped ESLint; Prettier; full TypeScript; `git diff --check`; live provider-free adversarial case completed in 100 ms with 0 ms RPC time; `eval:quality:release:offline` passed with zero blocking failures and zero model, request-ID, token, cost, or generation-latency evidence. Flaky local browser/composite suites intentionally not repeated; hosted CI remains authoritative. | | 2026-07-14 | PR #666 / codex/release-blocker-remediation | 9b56eebe4b23ab783207445fb827c317c8d59be8 + reviewed follow-up diff | review-followup | One late P2 retrieval-contract gap was confirmed: the optimized agitation query retained IM/PO but could drop other already-supported amount, route, and frequency aliases. Medication evidence intent is now shared with retrieval selection, and focused agitation queries preserve requested numeric units, SC, SL, PRN, and frequency signals without restoring the broad ten-term expansion. | GitHub review-thread inspection; focused clinical-search/retrieval Vitest 112/112; scoped ESLint; Prettier; `git diff --check`. Hosted final-head TypeScript/build/CI and exact-head staging evidence remain required after push. | -<<<<<<< HEAD | 2026-07-15 | PR #673 / cursor/audit-remediation-plan-0411 | d061beec41a906f5a3be761e719be0ecbd9279ea + review-followup | docs audit remediation plan review + CI | Hosted static-pr failed on Prettier for the new plan. Codex P1s: A2 scrub prove covered only ag_queries (now all four process-hardening tables); B2 staging shared prod RAG_QUERY_HASH_SECRET (now staging-only). CodeRabbit: format, Wave I coverage for client-env.ts / mockup-named components (P3-STRUCT1/2), and npm run prefixes for eval commands. | Focused Prettier on changed docs; git diff --check. Docs-only; no app code or provider checks. | -======= -| 2026-07-14 | codex/formulation-specifier-separation-current | 25d7f733f + uncommitted fix | Specifiers/Formulation boundary bug hunt and remediation | Confirmed two P1 clinical-navigation/content boundary defects: the completed Formulation change deleted the distinct psychiatric Specifiers workspace and redirected every `/specifiers/*` route into Formulation; a later universal-search contract also labelled Formulation mechanism results as `specifiers`. Restored the independent Specifiers catalogue, detail, builder, compare, and map surfaces; registered both app modes and action sets; kept submitted searches in their owning standalone workspace; and split universal search into real `specifiers` and `formulation` domains with correct records, headings, mode ownership, and destinations. | Offline only: full Vitest 2,404 passed/1 skipped, TypeScript, full ESLint, focused Vitest 56/56, sitemap check, `git diff --check`, production-readiness READY with expected missing-env warnings, identity-checked server at `http://localhost:3384`, and repaired Chromium journeys for Specifiers desktop/mobile, Formulation mobile, and explicit route-family separation all passed. No provider-backed or live API checks run. | ->>>>>>> origin/main +| 2026-07-14 | codex/formulation-specifier-separation-current | 25d7f733f + uncommitted fix | Specifiers/Formulation boundary bug hunt and remediation | Confirmed two P1 clinical-navigation/content boundary defects: the completed Formulation change deleted the distinct psychiatric Specifiers workspace and redirected every `/specifiers/*` route into Formulation; a later universal-search contract also labelled Formulation mechanism results as `specifiers`. Restored the independent Specifiers catalogue, detail, builder, compare, and map surfaces; registered both app modes and action sets; kept submitted searches in their owning standalone workspace; and split universal search into real `specifiers` and `formulation` domains with correct records, headings, mode ownership, and destinations. | Offline only: full Vitest 2,404 passed/1 skipped, TypeScript, full ESLint, focused Vitest 56/56, sitemap check, `git diff --check`, production-readiness READY with expected missing-env warnings, identity-checked server at `http://localhost:3384`, and repaired Chromium journeys for Specifiers desktop/mobile, Formulation mobile, and explicit route-family separation all passed. No provider-backed or live API checks run. | \ No newline at end of file From 6d4b946e981a9251aaeb12097b6edc9e9dab2b60 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 14 Jul 2026 18:30:46 +0000 Subject: [PATCH 5/8] docs: add 2026-07-14 audit findings handover Consolidate major and sub findings, skills run, checks, and next-step checklists into a single handover artifact linked from the remediation plan. Co-authored-by: BigSimmo --- docs/audit-handover-2026-07-14.md | 253 ++++++++++++++++++++++ docs/audit-remediation-plan-2026-07-14.md | 2 + docs/operator-backlog.md | 1 + 3 files changed, 256 insertions(+) create mode 100644 docs/audit-handover-2026-07-14.md diff --git a/docs/audit-handover-2026-07-14.md b/docs/audit-handover-2026-07-14.md new file mode 100644 index 000000000..bd65a6858 --- /dev/null +++ b/docs/audit-handover-2026-07-14.md @@ -0,0 +1,253 @@ +# Handover — repository audit findings (2026-07-14) + +**Purpose:** Single handoff artifact for the multi-skill repository audit. Use this to continue +remediation without re-running discovery. + +| Field | Value | +| --- | --- | +| Audit HEAD | `570e6ba56ae60bea56a32801b9cc96c5a8dfde4f` (`main` at audit time) | +| Audit date | 2026-07-14 | +| Mode | Pure review (no app-code fixes applied as part of the audit) | +| Remediation plan | [`audit-remediation-plan-2026-07-14.md`](audit-remediation-plan-2026-07-14.md) | +| Ledger rows | [`branch-review-ledger.md`](branch-review-ledger.md) (multi-skill + consolidated rows) | +| Plan PR | [#673](https://github.com/BigSimmo/Database/pull/673) (`cursor/audit-remediation-plan-0411`) | +| Ledger-only PR | [#652](https://github.com/BigSimmo/Database/pull/652) (`cursor/repo-wide-audit-ledger-0411`) | + +--- + +## 1. Verdict + +- **No confirmed P0/P1 defects in application source** that equal an active cross-tenant breach or + clinical governance bypass. +- **Active majors** are mostly **operator / legal / launch-confidence** blockers plus one **code P1** + availability gap (anonymous catalog rate-limit bypass). +- Offline structure gate was green at audit time (`verify:cheap` ~2,290 tests / 2 skipped). +- Provider-backed gates (`check:drift`, golden evals, `verify:release`, live advisors) were **not** + run (API confirmation boundary). + +**Highest residual risks if nothing else is fixed:** APP 8 overseas processing, unproven release +evals, service-role tenancy regression class, upstream OCR quality labels driving danger refusal. + +--- + +## 2. Skills run + +| Skill / pass | Outcome summary | +| --- | --- | +| Repo auditor | No P0/P1 structural defects; P2 env/orphan/bundle | +| Security review | No confirmed IDOR P0; P1 privacy (PIA-1); P2 DTO/tenancy/auth UI | +| Clinical governance | No P0/P1; fail-closed answer path looks solid; P3 ranking notes | +| RAG retrieval | D4/D5 default-OFF confirmed safe; P3 demotion floor | +| Ingestion worker | No P0/P1; P2 unreachable commit fallback, recovery crash, unwired gate | +| API review | P1 anon catalog rate-limit bypass; P2 envelopes/limits/summarize | +| Frontend UI / a11y | No P0; P2 describedby / icons / Tab / live region | +| Release readiness | Launch blocked on OPS/LEGAL; backlog drift vs runbooks | +| Testing / code quality | CI scope holes; soft critical safety assert; flake debt | + +--- + +## 3. Active major issues + +### M1 — PIA-1 overseas processing (OPERATOR / LEGAL — P0 for real-patient use) + +| | | +| --- | --- | +| **What** | Queries (and excerpts when synthesizing) process via Railway Singapore + OpenAI US | +| **Where** | `docs/privacy-impact-assessment.md`, `docs/openai-cross-border-basis.md` | +| **Risk** | Sensitive health info overseas without closed APP 8 / contractual basis | +| **Address** | Wave B1 in remediation plan — DPA / ZDR / processor record + counsel | +| **Status at handover** | Open (DPA rows still `_no_`) | + +### M2 — PIA-2 `RAG_QUERY_HASH_SECRET` on Railway (OPERATOR — P1) + +| | | +| --- | --- | +| **What** | Code fails closed without secret; GitHub has it; Railway runtime still `🔎 verify` | +| **Where** | `docs/operator-backlog.md`, `src/lib/env.ts` / instrumentation boot guard | +| **Risk** | Boot fail or weak query hashing in prod | +| **Address** | Wave B2 — confirm same ≥16-char value in Railway | +| **Status at handover** | Verify pending | + +### M3 — Release gate / evals / canary / staging incomplete (OPERATOR — P1) + +| | | +| --- | --- | +| **What** | `verify:release`, golden retrieval 36/36, rag-only quality, staging soak, Eval Canary two greens not closed; prior OpenAI embedding quota exhaustion | +| **Where** | `docs/operator-backlog.md`, `docs/launch-operator-runbook.md` | +| **Risk** | Cannot claim clinical release confidence | +| **Address** | Waves B3–B4 + J | +| **Status at handover** | Pending | + +### M4 — Anonymous public catalogs skip rate limits (CODE — P1) + +| | | +| --- | --- | +| **What** | No-auth catalog GETs short-circuit before rate limiting; large JSON possible | +| **Where** | `src/lib/public-api-access.ts:73–76` (`shouldResolvePublicCatalogAccess`); e.g. `src/app/api/medications/route.ts:108+`; same pattern registry/differentials | +| **Risk** | Egress/CPU DoS by omitting auth | +| **Address** | Wave C1 — always rate-limit anon + prefer `fields=index` default | +| **Prove** | Unauth GET still hits limiter; payload size capped | +| **Status at handover** | Unfixed | + +### M5 — Operator backlog vs runbook drift (PROCESS — P1 for ops error) + +| | | +| --- | --- | +| **What** | Backlog still `⏳` for items runbook/other docs treat as done/verify | +| **Where** | `docs/operator-backlog.md` vs `docs/launch-operator-runbook.md`, empty `supabase/drift-allowlist.json` | +| **Risk** | Wrong re-apply or false unfinished state | +| **Address** | Wave A1 — reconcile with live migration list / drift | +| **Status at handover** | Open | + +### M6 — Single-layer tenancy (ARCHITECTURE — P1 regression class) + +| | | +| --- | --- | +| **What** | APIs use service role; ownership enforced in app helpers/RPCs, not full second-layer RLS for public-overlay model | +| **Where** | `createAdminClient()`, `withOwnerReadScope`, `retrieval_owner_matches`; `docs/tenancy-defense-in-depth-review.md` | +| **Risk** | Future route forgetting owner scope → private corpus leak | +| **Address** | Wave D2 — CI unscoped-query guard + live A/B | +| **Status at handover** | Mitigated in current routes; guard not yet coded | + +--- + +## 4. Sub-issues (P2) + +### 4.1 Security / privacy / API + +| ID | Finding | Evidence | Address wave | +| --- | --- | --- | --- | +| S1 | Authed public-doc DTOs leak `storage_path` / `content_hash` | `documents/route.ts`, `documents/[id]/route.ts` | D1 | +| S2 | Auth UI trusts `getSession()`; APIs use `getUser` | `src/lib/supabase/client.tsx` | D3 | +| S3 | Public-upload quarantine pool high blast radius if enabled | `upload/route.ts` + pool owner | D4 | +| S4 | Auth 401 envelope `{ error }` only | `supabase/auth.ts` vs `http.ts` | H1 | +| S5 | Many hand-rolled `{ error }` omit `code`/`message` | upload, doc 404s, feedback, demo | H1 | +| S6 | Auth write/admin routes lack rate limits | bulk, labels, table-facts, ingestion/*, eval-cases, jobs | H2 | +| S7 | Document list offset max `1_000_000` | `documents/route.ts` | H3 | +| S8 | Upload reserves max body when Content-Length absent | `upload/route.ts` | H4 | +| S9 | Authed stream summarize excludes public docs | `answer/stream` → `summarizeDocument` owner-only | H5 | +| S10 | Bulk returns raw DB `error.message` | `documents/bulk/route.ts` | D5 | +| S11 | `/api/jobs` can return demo jobs when env missing | `jobs/route.ts` | H6 | + +### 4.2 Ingestion / indexing + +| ID | Finding | Evidence | Address wave | +| --- | --- | --- | --- | +| I1 | Unreachable commit fallback after `throw` | `worker/main.ts:544–547` | E1 | +| I2 | Recovery `pending`+`failed` → two retries → unique index crash | `ingestion-recovery.ts`, `recover-ingestion-queue.ts` | E2 | +| I3 | `decideReindexGate` never wired into `scripts/reindex.ts` | grep shows no production driver | E3 | + +### 4.3 Structure / config / CI / testing + +| ID | Finding | Evidence | Address wave | +| --- | --- | --- | --- | +| C1 | `.env.example` weak-OR flag `true` vs code default `false` | env example vs `env.ts` | F5 | +| C2 | Bundle budget unenforced (`enforce: false`, null baseline) | `bundle-budget.json` | F6 | +| C3 | CI `ui_changed` skips `app-modes` / `ui-copy` / route helpers | probed `ui_changed: false` | F1 | +| C4 | CI `rag_eval` skips `clinical-safety` / privacy libs | probed `rag_eval_changed: false` | F2 | +| C5 | Known flakes in blocking suite; `@quarantine` unused | `flake-ledger.json` (4) | F4 | +| C6 | Soft `@critical` safety UI assert | `ui-smoke.spec.ts` optional trigger | F3 | +| C7 | `waitForTimeout` / swallowed `networkidle` in UI smoke | `ui-smoke.spec.ts` | F4 | +| C8 | `reindex-eval-gate.ts` production orphan (~488 lines) | tests only | E3 / I P3 | + +### 4.4 Frontend / a11y + +| ID | Finding | Evidence | Address wave | +| --- | --- | --- | --- | +| U1 | `aria-describedby` → `aria-hidden` description (dead) | `mode-action-popup.tsx` | G1 | +| U2 | ~25 dynamic Lucide `` missing `aria-hidden` | ESLint rule gap | G2 | +| U3 | Mode menu does not close on Tab | `master-search-header.tsx` | G3 | +| U4 | No live region for answer ready / stream complete | answer surfaces | G4 | + +--- + +## 5. Sub-issues (P3) + +| ID | Finding | Notes | +| --- | --- | --- | +| P3-R1 | Demotion floor `Math.max(hybrid_score, boosted)` at `rag.ts:663` | Weakens D4 when activated | +| P3-R2 | D4 hits corpus-wide `document_status: "unknown"` default | Activation needs golden eval | +| P3-R3 | `outdatedPenalty` ON vs “no governance weighting” wording | Demotion-only, documented tension | +| P3-I1 | Bare `updateJob` without lease filter | Cosmetic race | +| P3-I2 | Cleanup RPC still uses JSONB path vs typed column | Forward migrate | +| P3-I3 | NULL `p_worker_id` bypasses lease in RPCs | Footgun | +| P3-S1 | Signed URL 10‑min bearer lifetime | Product threat model | +| P3-S2 | Setup-status detail for any authed user | Info disclosure | +| P3-S3 | Auth callback reflects provider errors into URL | Minor leak/UX | +| P3-U1–U4 | Forced-colors glass/gloss; Sheet overlay token; reduced-motion scale; brand hex adjust | UI polish | +| P3-C1 | Duplicate `test:e2e` / `test:e2e:all` | Alias cleanup | +| P3-C2 | Spend/price env vars missing from `.env.example` | Docs | +| P3-PERF | Hybrid RPC latency tail | Capacity workstream | +| P3-SIZE | Large modules (`rag.ts`, dashboard, document viewer) | Continue decomp when touching | + +Full address sketches: Wave I in the remediation plan. + +--- + +## 6. What looks solid (do not “fix” casually) + +- Fail-closed `buildGovernedAnswerClientResponse` on `/api/answer` and stream; no provisional + clinical prose before governance +- Numeric / quote / citation verification; prompt-injection sanitization +- Owner-scope fail-closed helpers; query hash redaction defaults +- D4/D5 (#649) default-OFF and fail-safe +- Architecture-boundaries: no import cycles; server modules isolated from client graph +- July-8 ingestion batch marked live-verified in backlog (`✅`) +- Offline cheap gate green at audit HEAD + +--- + +## 7. Checks run / not run + +| Ran | Not run (need confirmation) | +| --- | --- | +| Specialist static audits | Live Supabase / OpenAI / Railway | +| Structure `verify:cheap` (reported green) | `check:drift`, `check:supabase-project` | +| Focused Vitest (governance, RAG, ingestion suites) | `eval:retrieval:quality`, `eval:quality --rag-only` | +| `ci-change-scope` probes for scope holes | `verify:release`, `verify:ui` | +| | `check:production-readiness` with live secrets | +| | Security advisors / live A/B tenancy | + +--- + +## 8. Suggested next actions for the receiving agent + +1. Read this file + [`audit-remediation-plan-2026-07-14.md`](audit-remediation-plan-2026-07-14.md). +2. **Do not** re-audit the whole repo unless HEAD changed materially — check ledger first. +3. Prefer first CODE PR: **C1 + D1** (catalog rate limits + public DTO redaction). +4. Parallel OPS track: Wave A reconcile backlog, then B2 secret verify, then B1 legal. +5. Keep PRs one theme each (see remediation plan “Suggested PR granularity”). +6. After each wave: update this handover’s status notes, flip backlog rows, append ledger. + +### Suggested first CODE PR checklist + +- [ ] C1 anonymous catalog rate limits (+ tests) +- [ ] D1 redact `storage_path` / `content_hash` for non-owner public docs +- [ ] `npm run verify:cheap` + focused route tests +- [ ] `npm run verify:pr-local` before handoff +- [ ] Link PR body to finding IDs (`M4`, `S1`) + +--- + +## 9. Related references + +| Doc | Role | +| --- | --- | +| [`audit-remediation-plan-2026-07-14.md`](audit-remediation-plan-2026-07-14.md) | How to fix, sequenced | +| [`operator-backlog.md`](operator-backlog.md) | Human/provider actions index | +| [`launch-operator-runbook.md`](launch-operator-runbook.md) | Sequenced launch commands | +| [`privacy-impact-assessment.md`](privacy-impact-assessment.md) | PIA register | +| [`openai-cross-border-basis.md`](openai-cross-border-basis.md) | APP 8 checklist | +| [`tenancy-defense-in-depth-review.md`](tenancy-defense-in-depth-review.md) | Tenancy defense notes | +| [`process-hardening.md`](process-hardening.md) | Process & known debts | +| [`codex-review-protocol.md`](codex-review-protocol.md) | Review severity / mutation rules | +| [`branch-review-ledger.md`](branch-review-ledger.md) | Prevent repeat audits | + +--- + +## 10. Explicit non-actions at handover + +- No application source was patched for these findings in the audit session. +- No live Supabase/OpenAI/Railway mutations were performed. +- No merge to `main`, force-push, or branch cleanup. +- Remediations require separate feature PRs per wave/theme. diff --git a/docs/audit-remediation-plan-2026-07-14.md b/docs/audit-remediation-plan-2026-07-14.md index 487343b05..c394aa165 100644 --- a/docs/audit-remediation-plan-2026-07-14.md +++ b/docs/audit-remediation-plan-2026-07-14.md @@ -4,6 +4,8 @@ Plan derived from the multi-skill repository audit of `main` @ `570e6ba` (ledger [`branch-review-ledger.md`](branch-review-ledger.md)). This document is the sequenced address plan for every finding: major blockers first, then P2 sub-issues, then P3 cleanup. +**Findings handover (inventory + status):** [`audit-handover-2026-07-14.md`](audit-handover-2026-07-14.md). + **Rules for executing this plan** - Prefer the smallest safe change per finding; do not bundle unrelated domains in one PR. diff --git a/docs/operator-backlog.md b/docs/operator-backlog.md index 98fdff33a..2ac3a23b9 100644 --- a/docs/operator-backlog.md +++ b/docs/operator-backlog.md @@ -10,6 +10,7 @@ Update the row (and its runbook) when an action lands. The sequenced flow with e approval gates is [launch-operator-runbook.md](launch-operator-runbook.md); this table is the index. Code + ops remediation waves from the 2026-07-14 multi-skill audit live in [audit-remediation-plan-2026-07-14.md](audit-remediation-plan-2026-07-14.md). +Findings inventory for handover: [audit-handover-2026-07-14.md](audit-handover-2026-07-14.md). > Status column is seeded from repo runbooks + session memory and **must be confirmed against live > state** before acting — do not treat a `🔎 verify` row as authoritative. From b26fd1a40c0a7fb119c84d69f0aa1532e3cab72c Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 19:06:06 +0000 Subject: [PATCH 6/8] fix: apply CodeRabbit auto-fixes Fixed 1 file(s) based on 3 unresolved review comments. Co-authored-by: CodeRabbit --- docs/audit-handover-2026-07-14.md | 104 ++++++++++++++++-------------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/docs/audit-handover-2026-07-14.md b/docs/audit-handover-2026-07-14.md index bd65a6858..44c156ce4 100644 --- a/docs/audit-handover-2026-07-14.md +++ b/docs/audit-handover-2026-07-14.md @@ -18,9 +18,11 @@ remediation without re-running discovery. ## 1. Verdict - **No confirmed P0/P1 defects in application source** that equal an active cross-tenant breach or - clinical governance bypass. -- **Active majors** are mostly **operator / legal / launch-confidence** blockers plus one **code P1** - availability gap (anonymous catalog rate-limit bypass). + clinical governance bypass. This scoping is deliberate: other P1-severity issues exist but fall + outside those two defect classes. +- **Active majors** include six items: five **operator / legal / launch-confidence** blockers (M1-M3, + M5-M6) plus one **code P1 availability defect** (M4: anonymous catalog rate-limit bypass). The code + P1 must not be under-prioritized relative to the operator/legal items. - Offline structure gate was green at audit time (`verify:cheap` ~2,290 tests / 2 skipped). - Provider-backed gates (`check:drift`, golden evals, `verify:release`, live advisors) were **not** run (API confirmation boundary). @@ -55,7 +57,7 @@ evals, service-role tenancy regression class, upstream OCR quality labels drivin | **What** | Queries (and excerpts when synthesizing) process via Railway Singapore + OpenAI US | | **Where** | `docs/privacy-impact-assessment.md`, `docs/openai-cross-border-basis.md` | | **Risk** | Sensitive health info overseas without closed APP 8 / contractual basis | -| **Address** | Wave B1 in remediation plan — DPA / ZDR / processor record + counsel | +| **Address** | Remediation Wave B1 — DPA / ZDR / processor record + counsel | | **Status at handover** | Open (DPA rows still `_no_`) | ### M2 — PIA-2 `RAG_QUERY_HASH_SECRET` on Railway (OPERATOR — P1) @@ -65,7 +67,7 @@ evals, service-role tenancy regression class, upstream OCR quality labels drivin | **What** | Code fails closed without secret; GitHub has it; Railway runtime still `🔎 verify` | | **Where** | `docs/operator-backlog.md`, `src/lib/env.ts` / instrumentation boot guard | | **Risk** | Boot fail or weak query hashing in prod | -| **Address** | Wave B2 — confirm same ≥16-char value in Railway | +| **Address** | Remediation Wave B2 — confirm same ≥16-char value in Railway | | **Status at handover** | Verify pending | ### M3 — Release gate / evals / canary / staging incomplete (OPERATOR — P1) @@ -75,7 +77,7 @@ evals, service-role tenancy regression class, upstream OCR quality labels drivin | **What** | `verify:release`, golden retrieval 36/36, rag-only quality, staging soak, Eval Canary two greens not closed; prior OpenAI embedding quota exhaustion | | **Where** | `docs/operator-backlog.md`, `docs/launch-operator-runbook.md` | | **Risk** | Cannot claim clinical release confidence | -| **Address** | Waves B3–B4 + J | +| **Address** | Remediation Waves B3–B4 + J | | **Status at handover** | Pending | ### M4 — Anonymous public catalogs skip rate limits (CODE — P1) @@ -85,7 +87,7 @@ evals, service-role tenancy regression class, upstream OCR quality labels drivin | **What** | No-auth catalog GETs short-circuit before rate limiting; large JSON possible | | **Where** | `src/lib/public-api-access.ts:73–76` (`shouldResolvePublicCatalogAccess`); e.g. `src/app/api/medications/route.ts:108+`; same pattern registry/differentials | | **Risk** | Egress/CPU DoS by omitting auth | -| **Address** | Wave C1 — always rate-limit anon + prefer `fields=index` default | +| **Address** | Remediation Wave C1 — always rate-limit anon + prefer `fields=index` default | | **Prove** | Unauth GET still hits limiter; payload size capped | | **Status at handover** | Unfixed | @@ -96,7 +98,7 @@ evals, service-role tenancy regression class, upstream OCR quality labels drivin | **What** | Backlog still `⏳` for items runbook/other docs treat as done/verify | | **Where** | `docs/operator-backlog.md` vs `docs/launch-operator-runbook.md`, empty `supabase/drift-allowlist.json` | | **Risk** | Wrong re-apply or false unfinished state | -| **Address** | Wave A1 — reconcile with live migration list / drift | +| **Address** | Remediation Wave A1 — reconcile with live migration list / drift | | **Status at handover** | Open | ### M6 — Single-layer tenancy (ARCHITECTURE — P1 regression class) @@ -106,7 +108,7 @@ evals, service-role tenancy regression class, upstream OCR quality labels drivin | **What** | APIs use service role; ownership enforced in app helpers/RPCs, not full second-layer RLS for public-overlay model | | **Where** | `createAdminClient()`, `withOwnerReadScope`, `retrieval_owner_matches`; `docs/tenancy-defense-in-depth-review.md` | | **Risk** | Future route forgetting owner scope → private corpus leak | -| **Address** | Wave D2 — CI unscoped-query guard + live A/B | +| **Address** | Remediation Wave D2 — CI unscoped-query guard + live A/B | | **Status at handover** | Mitigated in current routes; guard not yet coded | --- @@ -115,49 +117,49 @@ evals, service-role tenancy regression class, upstream OCR quality labels drivin ### 4.1 Security / privacy / API -| ID | Finding | Evidence | Address wave | +| ID | Finding | Evidence | Remediation wave | | --- | --- | --- | --- | -| S1 | Authed public-doc DTOs leak `storage_path` / `content_hash` | `documents/route.ts`, `documents/[id]/route.ts` | D1 | -| S2 | Auth UI trusts `getSession()`; APIs use `getUser` | `src/lib/supabase/client.tsx` | D3 | -| S3 | Public-upload quarantine pool high blast radius if enabled | `upload/route.ts` + pool owner | D4 | -| S4 | Auth 401 envelope `{ error }` only | `supabase/auth.ts` vs `http.ts` | H1 | -| S5 | Many hand-rolled `{ error }` omit `code`/`message` | upload, doc 404s, feedback, demo | H1 | -| S6 | Auth write/admin routes lack rate limits | bulk, labels, table-facts, ingestion/*, eval-cases, jobs | H2 | -| S7 | Document list offset max `1_000_000` | `documents/route.ts` | H3 | -| S8 | Upload reserves max body when Content-Length absent | `upload/route.ts` | H4 | -| S9 | Authed stream summarize excludes public docs | `answer/stream` → `summarizeDocument` owner-only | H5 | -| S10 | Bulk returns raw DB `error.message` | `documents/bulk/route.ts` | D5 | -| S11 | `/api/jobs` can return demo jobs when env missing | `jobs/route.ts` | H6 | +| S1 | Authed public-doc DTOs leak `storage_path` / `content_hash` | `documents/route.ts`, `documents/[id]/route.ts` | Wave D1 | +| S2 | Auth UI trusts `getSession()`; APIs use `getUser` | `src/lib/supabase/client.tsx` | Wave D3 | +| S3 | Public-upload quarantine pool high blast radius if enabled | `upload/route.ts` + pool owner | Wave D4 | +| S4 | Auth 401 envelope `{ error }` only | `supabase/auth.ts` vs `http.ts` | Wave H1 | +| S5 | Many hand-rolled `{ error }` omit `code`/`message` | upload, doc 404s, feedback, demo | Wave H1 | +| S6 | Auth write/admin routes lack rate limits | bulk, labels, table-facts, ingestion/*, eval-cases, jobs | Wave H2 | +| S7 | Document list offset max `1_000_000` | `documents/route.ts` | Wave H3 | +| S8 | Upload reserves max body when Content-Length absent | `upload/route.ts` | Wave H4 | +| S9 | Authed stream summarize excludes public docs | `answer/stream` → `summarizeDocument` owner-only | Wave H5 | +| S10 | Bulk returns raw DB `error.message` | `documents/bulk/route.ts` | Wave D5 | +| S11 | `/api/jobs` can return demo jobs when env missing | `jobs/route.ts` | Wave H6 | ### 4.2 Ingestion / indexing -| ID | Finding | Evidence | Address wave | +| ID | Finding | Evidence | Remediation wave | | --- | --- | --- | --- | -| I1 | Unreachable commit fallback after `throw` | `worker/main.ts:544–547` | E1 | -| I2 | Recovery `pending`+`failed` → two retries → unique index crash | `ingestion-recovery.ts`, `recover-ingestion-queue.ts` | E2 | -| I3 | `decideReindexGate` never wired into `scripts/reindex.ts` | grep shows no production driver | E3 | +| I1 | Unreachable commit fallback after `throw` | `worker/main.ts:544–547` | Wave E1 | +| I2 | Recovery `pending`+`failed` → two retries → unique index crash | `ingestion-recovery.ts`, `recover-ingestion-queue.ts` | Wave E2 | +| I3 | `decideReindexGate` never wired into `scripts/reindex.ts` | grep shows no production driver | Wave E3 | ### 4.3 Structure / config / CI / testing -| ID | Finding | Evidence | Address wave | +| ID | Finding | Evidence | Remediation wave | | --- | --- | --- | --- | -| C1 | `.env.example` weak-OR flag `true` vs code default `false` | env example vs `env.ts` | F5 | -| C2 | Bundle budget unenforced (`enforce: false`, null baseline) | `bundle-budget.json` | F6 | -| C3 | CI `ui_changed` skips `app-modes` / `ui-copy` / route helpers | probed `ui_changed: false` | F1 | -| C4 | CI `rag_eval` skips `clinical-safety` / privacy libs | probed `rag_eval_changed: false` | F2 | -| C5 | Known flakes in blocking suite; `@quarantine` unused | `flake-ledger.json` (4) | F4 | -| C6 | Soft `@critical` safety UI assert | `ui-smoke.spec.ts` optional trigger | F3 | -| C7 | `waitForTimeout` / swallowed `networkidle` in UI smoke | `ui-smoke.spec.ts` | F4 | -| C8 | `reindex-eval-gate.ts` production orphan (~488 lines) | tests only | E3 / I P3 | +| C1 | `.env.example` weak-OR flag `true` vs code default `false` | env example vs `env.ts` | Wave F5 | +| C2 | Bundle budget unenforced (`enforce: false`, null baseline) | `bundle-budget.json` | Wave F6 | +| C3 | CI `ui_changed` skips `app-modes` / `ui-copy` / route helpers | probed `ui_changed: false` | Wave F1 | +| C4 | CI `rag_eval` skips `clinical-safety` / privacy libs | probed `rag_eval_changed: false` | Wave F2 | +| C5 | Known flakes in blocking suite; `@quarantine` unused | `flake-ledger.json` (4) | Wave F4 | +| C6 | Soft `@critical` safety UI assert | `ui-smoke.spec.ts` optional trigger | Wave F3 | +| C7 | `waitForTimeout` / swallowed `networkidle` in UI smoke | `ui-smoke.spec.ts` | Wave F4 | +| C8 | `reindex-eval-gate.ts` production orphan (~488 lines) | tests only | Wave E3 / Wave I P3 | ### 4.4 Frontend / a11y -| ID | Finding | Evidence | Address wave | +| ID | Finding | Evidence | Remediation wave | | --- | --- | --- | --- | -| U1 | `aria-describedby` → `aria-hidden` description (dead) | `mode-action-popup.tsx` | G1 | -| U2 | ~25 dynamic Lucide `` missing `aria-hidden` | ESLint rule gap | G2 | -| U3 | Mode menu does not close on Tab | `master-search-header.tsx` | G3 | -| U4 | No live region for answer ready / stream complete | answer surfaces | G4 | +| U1 | `aria-describedby` → `aria-hidden` description (dead) | `mode-action-popup.tsx` | Wave G1 | +| U2 | ~25 dynamic Lucide `` missing `aria-hidden` | ESLint rule gap | Wave G2 | +| U3 | Mode menu does not close on Tab | `master-search-header.tsx` | Wave G3 | +| U4 | No live region for answer ready / stream complete | answer surfaces | Wave G4 | --- @@ -165,9 +167,9 @@ evals, service-role tenancy regression class, upstream OCR quality labels drivin | ID | Finding | Notes | | --- | --- | --- | -| P3-R1 | Demotion floor `Math.max(hybrid_score, boosted)` at `rag.ts:663` | Weakens D4 when activated | -| P3-R2 | D4 hits corpus-wide `document_status: "unknown"` default | Activation needs golden eval | -| P3-R3 | `outdatedPenalty` ON vs “no governance weighting” wording | Demotion-only, documented tension | +| P3-R1 | Demotion floor `Math.max(hybrid_score, boosted)` at `rag.ts:663` | Weakens feature D4 when activated | +| P3-R2 | Feature D4 hits corpus-wide `document_status: "unknown"` default | Activation needs golden eval | +| P3-R3 | `outdatedPenalty` ON vs "no governance weighting" wording | Demotion-only, documented tension | | P3-I1 | Bare `updateJob` without lease filter | Cosmetic race | | P3-I2 | Cleanup RPC still uses JSONB path vs typed column | Forward migrate | | P3-I3 | NULL `p_worker_id` bypasses lease in RPCs | Footgun | @@ -180,7 +182,7 @@ evals, service-role tenancy regression class, upstream OCR quality labels drivin | P3-PERF | Hybrid RPC latency tail | Capacity workstream | | P3-SIZE | Large modules (`rag.ts`, dashboard, document viewer) | Continue decomp when touching | -Full address sketches: Wave I in the remediation plan. +Full address sketches: Remediation Wave I in the remediation plan. --- @@ -214,18 +216,20 @@ Full address sketches: Wave I in the remediation plan. 1. Read this file + [`audit-remediation-plan-2026-07-14.md`](audit-remediation-plan-2026-07-14.md). 2. **Do not** re-audit the whole repo unless HEAD changed materially — check ledger first. -3. Prefer first CODE PR: **C1 + D1** (catalog rate limits + public DTO redaction). -4. Parallel OPS track: Wave A reconcile backlog, then B2 secret verify, then B1 legal. -5. Keep PRs one theme each (see remediation plan “Suggested PR granularity”). -6. After each wave: update this handover’s status notes, flip backlog rows, append ledger. +3. Prefer first CODE PR: **Remediation Wave C1 + Wave D1** (catalog rate limits + public DTO redaction). +4. Parallel OPS track: Wave A reconcile backlog, then Wave B2 secret verify, then Wave B1 legal. +5. Keep PRs one theme each (see remediation plan "Suggested PR granularity"). +6. After each wave: update this handover's status notes, flip backlog rows, and update the existing + ledger row for the current branch/ref. Only append a new ledger row when the branch/ref, HEAD SHA, + or review scope changes, or when a fresh review is explicitly requested. ### Suggested first CODE PR checklist -- [ ] C1 anonymous catalog rate limits (+ tests) -- [ ] D1 redact `storage_path` / `content_hash` for non-owner public docs +- [ ] Remediation Wave C1: anonymous catalog rate limits (finding M4) (+ tests) +- [ ] Remediation Wave D1: redact `storage_path` / `content_hash` for non-owner public docs (finding S1) - [ ] `npm run verify:cheap` + focused route tests - [ ] `npm run verify:pr-local` before handoff -- [ ] Link PR body to finding IDs (`M4`, `S1`) +- [ ] Link PR body to finding IDs (M4, S1) --- From 6ba45597827dd635bbf82d7229b11c926abec796 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 15 Jul 2026 03:54:48 +0800 Subject: [PATCH 7/8] fix(docs): resolve audit plan review findings --- docs/audit-handover-2026-07-14.md | 268 +++++++++++----------- docs/audit-remediation-plan-2026-07-14.md | 12 +- docs/branch-review-ledger.md | 6 +- 3 files changed, 142 insertions(+), 144 deletions(-) diff --git a/docs/audit-handover-2026-07-14.md b/docs/audit-handover-2026-07-14.md index 44c156ce4..42579a98a 100644 --- a/docs/audit-handover-2026-07-14.md +++ b/docs/audit-handover-2026-07-14.md @@ -3,23 +3,23 @@ **Purpose:** Single handoff artifact for the multi-skill repository audit. Use this to continue remediation without re-running discovery. -| Field | Value | -| --- | --- | -| Audit HEAD | `570e6ba56ae60bea56a32801b9cc96c5a8dfde4f` (`main` at audit time) | -| Audit date | 2026-07-14 | -| Mode | Pure review (no app-code fixes applied as part of the audit) | -| Remediation plan | [`audit-remediation-plan-2026-07-14.md`](audit-remediation-plan-2026-07-14.md) | -| Ledger rows | [`branch-review-ledger.md`](branch-review-ledger.md) (multi-skill + consolidated rows) | -| Plan PR | [#673](https://github.com/BigSimmo/Database/pull/673) (`cursor/audit-remediation-plan-0411`) | -| Ledger-only PR | [#652](https://github.com/BigSimmo/Database/pull/652) (`cursor/repo-wide-audit-ledger-0411`) | +| Field | Value | +| ---------------- | -------------------------------------------------------------------------------------------- | +| Audit HEAD | `570e6ba56ae60bea56a32801b9cc96c5a8dfde4f` (`main` at audit time) | +| Audit date | 2026-07-14 | +| Mode | Pure review (no app-code fixes applied as part of the audit) | +| Remediation plan | [`audit-remediation-plan-2026-07-14.md`](audit-remediation-plan-2026-07-14.md) | +| Ledger rows | [`branch-review-ledger.md`](branch-review-ledger.md) (multi-skill + consolidated rows) | +| Plan PR | [#673](https://github.com/BigSimmo/Database/pull/673) (`cursor/audit-remediation-plan-0411`) | +| Ledger-only PR | [#652](https://github.com/BigSimmo/Database/pull/652) (`cursor/repo-wide-audit-ledger-0411`) | --- ## 1. Verdict -- **No confirmed P0/P1 defects in application source** that equal an active cross-tenant breach or - clinical governance bypass. This scoping is deliberate: other P1-severity issues exist but fall - outside those two defect classes. +- **No confirmed P0/P1 cross-tenant-breach or clinical-governance-bypass defects** were found in + application source. This scoping is deliberate: other P1-severity issues exist outside those two + defect classes. - **Active majors** include six items: five **operator / legal / launch-confidence** blockers (M1-M3, M5-M6) plus one **code P1 availability defect** (M4: anonymous catalog rate-limit bypass). The code P1 must not be under-prioritized relative to the operator/legal items. @@ -34,17 +34,17 @@ evals, service-role tenancy regression class, upstream OCR quality labels drivin ## 2. Skills run -| Skill / pass | Outcome summary | -| --- | --- | -| Repo auditor | No P0/P1 structural defects; P2 env/orphan/bundle | -| Security review | No confirmed IDOR P0; P1 privacy (PIA-1); P2 DTO/tenancy/auth UI | -| Clinical governance | No P0/P1; fail-closed answer path looks solid; P3 ranking notes | -| RAG retrieval | D4/D5 default-OFF confirmed safe; P3 demotion floor | -| Ingestion worker | No P0/P1; P2 unreachable commit fallback, recovery crash, unwired gate | -| API review | P1 anon catalog rate-limit bypass; P2 envelopes/limits/summarize | -| Frontend UI / a11y | No P0; P2 describedby / icons / Tab / live region | -| Release readiness | Launch blocked on OPS/LEGAL; backlog drift vs runbooks | -| Testing / code quality | CI scope holes; soft critical safety assert; flake debt | +| Skill / pass | Outcome summary | +| ---------------------- | ---------------------------------------------------------------------- | +| Repo auditor | No P0/P1 structural defects; P2 env/orphan/bundle | +| Security review | No confirmed IDOR P0; P1 privacy (PIA-1); P2 DTO/tenancy/auth UI | +| Clinical governance | No P0/P1; fail-closed answer path looks solid; P3 ranking notes | +| RAG retrieval | D4/D5 default-OFF confirmed safe; P3 demotion floor | +| Ingestion worker | No P0/P1; P2 unreachable commit fallback, recovery crash, unwired gate | +| API review | P1 anon catalog rate-limit bypass; P2 envelopes/limits/summarize | +| Frontend UI / a11y | No P0; P2 describedby / icons / Tab / live region | +| Release readiness | Launch blocked on OPS/LEGAL; backlog drift vs runbooks | +| Testing / code quality | CI scope holes; soft critical safety assert; flake debt | --- @@ -52,64 +52,64 @@ evals, service-role tenancy regression class, upstream OCR quality labels drivin ### M1 — PIA-1 overseas processing (OPERATOR / LEGAL — P0 for real-patient use) -| | | -| --- | --- | -| **What** | Queries (and excerpts when synthesizing) process via Railway Singapore + OpenAI US | -| **Where** | `docs/privacy-impact-assessment.md`, `docs/openai-cross-border-basis.md` | -| **Risk** | Sensitive health info overseas without closed APP 8 / contractual basis | -| **Address** | Remediation Wave B1 — DPA / ZDR / processor record + counsel | -| **Status at handover** | Open (DPA rows still `_no_`) | +| | | +| ---------------------- | ---------------------------------------------------------------------------------- | +| **What** | Queries (and excerpts when synthesizing) process via Railway Singapore + OpenAI US | +| **Where** | `docs/privacy-impact-assessment.md`, `docs/openai-cross-border-basis.md` | +| **Risk** | Sensitive health info overseas without closed APP 8 / contractual basis | +| **Address** | Remediation Wave B1 — DPA / ZDR / processor record + counsel | +| **Status at handover** | Open (DPA rows still `_no_`) | ### M2 — PIA-2 `RAG_QUERY_HASH_SECRET` on Railway (OPERATOR — P1) -| | | -| --- | --- | -| **What** | Code fails closed without secret; GitHub has it; Railway runtime still `🔎 verify` | -| **Where** | `docs/operator-backlog.md`, `src/lib/env.ts` / instrumentation boot guard | -| **Risk** | Boot fail or weak query hashing in prod | -| **Address** | Remediation Wave B2 — confirm same ≥16-char value in Railway | -| **Status at handover** | Verify pending | +| | | +| ---------------------- | ---------------------------------------------------------------------------------- | +| **What** | Code fails closed without secret; GitHub has it; Railway runtime still `🔎 verify` | +| **Where** | `docs/operator-backlog.md`, `src/lib/env.ts` / instrumentation boot guard | +| **Risk** | Boot fail or weak query hashing in prod | +| **Address** | Remediation Wave B2 — confirm same ≥16-char value in Railway | +| **Status at handover** | Verify pending | ### M3 — Release gate / evals / canary / staging incomplete (OPERATOR — P1) -| | | -| --- | --- | -| **What** | `verify:release`, golden retrieval 36/36, rag-only quality, staging soak, Eval Canary two greens not closed; prior OpenAI embedding quota exhaustion | -| **Where** | `docs/operator-backlog.md`, `docs/launch-operator-runbook.md` | -| **Risk** | Cannot claim clinical release confidence | -| **Address** | Remediation Waves B3–B4 + J | -| **Status at handover** | Pending | +| | | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| **What** | `verify:release`, golden retrieval 36/36, rag-only quality, staging soak, Eval Canary two greens not closed; prior OpenAI embedding quota exhaustion | +| **Where** | `docs/operator-backlog.md`, `docs/launch-operator-runbook.md` | +| **Risk** | Cannot claim clinical release confidence | +| **Address** | Remediation Waves B3–B4 + J | +| **Status at handover** | Pending | ### M4 — Anonymous public catalogs skip rate limits (CODE — P1) -| | | -| --- | --- | -| **What** | No-auth catalog GETs short-circuit before rate limiting; large JSON possible | -| **Where** | `src/lib/public-api-access.ts:73–76` (`shouldResolvePublicCatalogAccess`); e.g. `src/app/api/medications/route.ts:108+`; same pattern registry/differentials | -| **Risk** | Egress/CPU DoS by omitting auth | -| **Address** | Remediation Wave C1 — always rate-limit anon + prefer `fields=index` default | -| **Prove** | Unauth GET still hits limiter; payload size capped | -| **Status at handover** | Unfixed | +| | | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **What** | No-auth catalog GETs short-circuit before rate limiting; large JSON possible | +| **Where** | `src/lib/public-api-access.ts:73–76` (`shouldResolvePublicCatalogAccess`); e.g. `src/app/api/medications/route.ts:108+`; same pattern registry/differentials | +| **Risk** | Egress/CPU DoS by omitting auth | +| **Address** | Remediation Wave C1 — always rate-limit anon + prefer `fields=index` default | +| **Prove** | Unauth GET still hits limiter; payload size capped | +| **Status at handover** | Unfixed | ### M5 — Operator backlog vs runbook drift (PROCESS — P1 for ops error) -| | | -| --- | --- | -| **What** | Backlog still `⏳` for items runbook/other docs treat as done/verify | -| **Where** | `docs/operator-backlog.md` vs `docs/launch-operator-runbook.md`, empty `supabase/drift-allowlist.json` | -| **Risk** | Wrong re-apply or false unfinished state | -| **Address** | Remediation Wave A1 — reconcile with live migration list / drift | -| **Status at handover** | Open | +| | | +| ---------------------- | ------------------------------------------------------------------------------------------------------ | +| **What** | Backlog still `⏳` for items runbook/other docs treat as done/verify | +| **Where** | `docs/operator-backlog.md` vs `docs/launch-operator-runbook.md`, empty `supabase/drift-allowlist.json` | +| **Risk** | Wrong re-apply or false unfinished state | +| **Address** | Remediation Wave A1 — reconcile with live migration list / drift | +| **Status at handover** | Open | ### M6 — Single-layer tenancy (ARCHITECTURE — P1 regression class) -| | | -| --- | --- | -| **What** | APIs use service role; ownership enforced in app helpers/RPCs, not full second-layer RLS for public-overlay model | -| **Where** | `createAdminClient()`, `withOwnerReadScope`, `retrieval_owner_matches`; `docs/tenancy-defense-in-depth-review.md` | -| **Risk** | Future route forgetting owner scope → private corpus leak | -| **Address** | Remediation Wave D2 — CI unscoped-query guard + live A/B | -| **Status at handover** | Mitigated in current routes; guard not yet coded | +| | | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------- | +| **What** | APIs use service role; ownership enforced in app helpers/RPCs, not full second-layer RLS for public-overlay model | +| **Where** | `createAdminClient()`, `withOwnerReadScope`, `retrieval_owner_matches`; `docs/tenancy-defense-in-depth-review.md` | +| **Risk** | Future route forgetting owner scope → private corpus leak | +| **Address** | Remediation Wave D2 — CI unscoped-query guard + live A/B | +| **Status at handover** | Mitigated in current routes; guard not yet coded | --- @@ -117,70 +117,70 @@ evals, service-role tenancy regression class, upstream OCR quality labels drivin ### 4.1 Security / privacy / API -| ID | Finding | Evidence | Remediation wave | -| --- | --- | --- | --- | -| S1 | Authed public-doc DTOs leak `storage_path` / `content_hash` | `documents/route.ts`, `documents/[id]/route.ts` | Wave D1 | -| S2 | Auth UI trusts `getSession()`; APIs use `getUser` | `src/lib/supabase/client.tsx` | Wave D3 | -| S3 | Public-upload quarantine pool high blast radius if enabled | `upload/route.ts` + pool owner | Wave D4 | -| S4 | Auth 401 envelope `{ error }` only | `supabase/auth.ts` vs `http.ts` | Wave H1 | -| S5 | Many hand-rolled `{ error }` omit `code`/`message` | upload, doc 404s, feedback, demo | Wave H1 | -| S6 | Auth write/admin routes lack rate limits | bulk, labels, table-facts, ingestion/*, eval-cases, jobs | Wave H2 | -| S7 | Document list offset max `1_000_000` | `documents/route.ts` | Wave H3 | -| S8 | Upload reserves max body when Content-Length absent | `upload/route.ts` | Wave H4 | -| S9 | Authed stream summarize excludes public docs | `answer/stream` → `summarizeDocument` owner-only | Wave H5 | -| S10 | Bulk returns raw DB `error.message` | `documents/bulk/route.ts` | Wave D5 | -| S11 | `/api/jobs` can return demo jobs when env missing | `jobs/route.ts` | Wave H6 | +| ID | Finding | Evidence | Remediation wave | +| --- | ----------------------------------------------------------- | -------------------------------------------------------- | ---------------- | +| S1 | Authed public-doc DTOs leak `storage_path` / `content_hash` | `documents/route.ts`, `documents/[id]/route.ts` | Wave D1 | +| S2 | Auth UI trusts `getSession()`; APIs use `getUser` | `src/lib/supabase/client.tsx` | Wave D3 | +| S3 | Public-upload quarantine pool high blast radius if enabled | `upload/route.ts` + pool owner | Wave D4 | +| S4 | Auth 401 envelope `{ error }` only | `supabase/auth.ts` vs `http.ts` | Wave H1 | +| S5 | Many hand-rolled `{ error }` omit `code`/`message` | upload, doc 404s, feedback, demo | Wave H1 | +| S6 | Auth write/admin routes lack rate limits | bulk, labels, table-facts, ingestion/*, eval-cases, jobs | Wave H2 | +| S7 | Document list offset max `1_000_000` | `documents/route.ts` | Wave H3 | +| S8 | Upload reserves max body when Content-Length absent | `upload/route.ts` | Wave H4 | +| S9 | Authed stream summarize excludes public docs | `answer/stream` → `summarizeDocument` owner-only | Wave H5 | +| S10 | Bulk returns raw DB `error.message` | `documents/bulk/route.ts` | Wave D5 | +| S11 | `/api/jobs` can return demo jobs when env missing | `jobs/route.ts` | Wave H6 | ### 4.2 Ingestion / indexing -| ID | Finding | Evidence | Remediation wave | -| --- | --- | --- | --- | -| I1 | Unreachable commit fallback after `throw` | `worker/main.ts:544–547` | Wave E1 | -| I2 | Recovery `pending`+`failed` → two retries → unique index crash | `ingestion-recovery.ts`, `recover-ingestion-queue.ts` | Wave E2 | -| I3 | `decideReindexGate` never wired into `scripts/reindex.ts` | grep shows no production driver | Wave E3 | +| ID | Finding | Evidence | Remediation wave | +| --- | -------------------------------------------------------------- | ----------------------------------------------------- | ---------------- | +| I1 | Unreachable commit fallback after `throw` | `worker/main.ts:544–547` | Wave E1 | +| I2 | Recovery `pending`+`failed` → two retries → unique index crash | `ingestion-recovery.ts`, `recover-ingestion-queue.ts` | Wave E2 | +| I3 | `decideReindexGate` never wired into `scripts/reindex.ts` | grep shows no production driver | Wave E3 | ### 4.3 Structure / config / CI / testing -| ID | Finding | Evidence | Remediation wave | -| --- | --- | --- | --- | -| C1 | `.env.example` weak-OR flag `true` vs code default `false` | env example vs `env.ts` | Wave F5 | -| C2 | Bundle budget unenforced (`enforce: false`, null baseline) | `bundle-budget.json` | Wave F6 | -| C3 | CI `ui_changed` skips `app-modes` / `ui-copy` / route helpers | probed `ui_changed: false` | Wave F1 | -| C4 | CI `rag_eval` skips `clinical-safety` / privacy libs | probed `rag_eval_changed: false` | Wave F2 | -| C5 | Known flakes in blocking suite; `@quarantine` unused | `flake-ledger.json` (4) | Wave F4 | -| C6 | Soft `@critical` safety UI assert | `ui-smoke.spec.ts` optional trigger | Wave F3 | -| C7 | `waitForTimeout` / swallowed `networkidle` in UI smoke | `ui-smoke.spec.ts` | Wave F4 | -| C8 | `reindex-eval-gate.ts` production orphan (~488 lines) | tests only | Wave E3 / Wave I P3 | +| ID | Finding | Evidence | Remediation wave | +| ---------- | ------------------------------------------------------------- | ----------------------------------- | ------------------- | +| Finding C1 | `.env.example` weak-OR flag `true` vs code default `false` | env example vs `env.ts` | Wave F5 | +| C2 | Bundle budget unenforced (`enforce: false`, null baseline) | `bundle-budget.json` | Wave F6 | +| C3 | CI `ui_changed` skips `app-modes` / `ui-copy` / route helpers | probed `ui_changed: false` | Wave F1 | +| C4 | CI `rag_eval` skips `clinical-safety` / privacy libs | probed `rag_eval_changed: false` | Wave F2 | +| C5 | Known flakes in blocking suite; `@quarantine` unused | `flake-ledger.json` (4) | Wave F4 | +| C6 | Soft `@critical` safety UI assert | `ui-smoke.spec.ts` optional trigger | Wave F3 | +| C7 | `waitForTimeout` / swallowed `networkidle` in UI smoke | `ui-smoke.spec.ts` | Wave F4 | +| C8 | `reindex-eval-gate.ts` production orphan (~488 lines) | tests only | Wave E3 / Wave I P3 | ### 4.4 Frontend / a11y -| ID | Finding | Evidence | Remediation wave | -| --- | --- | --- | --- | -| U1 | `aria-describedby` → `aria-hidden` description (dead) | `mode-action-popup.tsx` | Wave G1 | -| U2 | ~25 dynamic Lucide `` missing `aria-hidden` | ESLint rule gap | Wave G2 | -| U3 | Mode menu does not close on Tab | `master-search-header.tsx` | Wave G3 | -| U4 | No live region for answer ready / stream complete | answer surfaces | Wave G4 | +| ID | Finding | Evidence | Remediation wave | +| --- | ----------------------------------------------------- | -------------------------- | ---------------- | +| U1 | `aria-describedby` → `aria-hidden` description (dead) | `mode-action-popup.tsx` | Wave G1 | +| U2 | ~25 dynamic Lucide `` missing `aria-hidden` | ESLint rule gap | Wave G2 | +| U3 | Mode menu does not close on Tab | `master-search-header.tsx` | Wave G3 | +| U4 | No live region for answer ready / stream complete | answer surfaces | Wave G4 | --- ## 5. Sub-issues (P3) -| ID | Finding | Notes | -| --- | --- | --- | -| P3-R1 | Demotion floor `Math.max(hybrid_score, boosted)` at `rag.ts:663` | Weakens feature D4 when activated | -| P3-R2 | Feature D4 hits corpus-wide `document_status: "unknown"` default | Activation needs golden eval | -| P3-R3 | `outdatedPenalty` ON vs "no governance weighting" wording | Demotion-only, documented tension | -| P3-I1 | Bare `updateJob` without lease filter | Cosmetic race | -| P3-I2 | Cleanup RPC still uses JSONB path vs typed column | Forward migrate | -| P3-I3 | NULL `p_worker_id` bypasses lease in RPCs | Footgun | -| P3-S1 | Signed URL 10‑min bearer lifetime | Product threat model | -| P3-S2 | Setup-status detail for any authed user | Info disclosure | -| P3-S3 | Auth callback reflects provider errors into URL | Minor leak/UX | -| P3-U1–U4 | Forced-colors glass/gloss; Sheet overlay token; reduced-motion scale; brand hex adjust | UI polish | -| P3-C1 | Duplicate `test:e2e` / `test:e2e:all` | Alias cleanup | -| P3-C2 | Spend/price env vars missing from `.env.example` | Docs | -| P3-PERF | Hybrid RPC latency tail | Capacity workstream | -| P3-SIZE | Large modules (`rag.ts`, dashboard, document viewer) | Continue decomp when touching | +| ID | Finding | Notes | +| -------- | -------------------------------------------------------------------------------------- | --------------------------------- | +| P3-R1 | Demotion floor `Math.max(hybrid_score, boosted)` at `rag.ts:663` | Weakens feature D4 when activated | +| P3-R2 | Feature D4 hits corpus-wide `document_status: "unknown"` default | Activation needs golden eval | +| P3-R3 | `outdatedPenalty` ON vs "no governance weighting" wording | Demotion-only, documented tension | +| P3-I1 | Bare `updateJob` without lease filter | Cosmetic race | +| P3-I2 | Cleanup RPC still uses JSONB path vs typed column | Forward migrate | +| P3-I3 | NULL `p_worker_id` bypasses lease in RPCs | Footgun | +| P3-S1 | Signed URL 10‑min bearer lifetime | Product threat model | +| P3-S2 | Setup-status detail for any authed user | Info disclosure | +| P3-S3 | Auth callback reflects provider errors into URL | Minor leak/UX | +| P3-U1–U4 | Forced-colors glass/gloss; Sheet overlay token; reduced-motion scale; brand hex adjust | UI polish | +| P3-C1 | Duplicate `test:e2e` / `test:e2e:all` | Alias cleanup | +| P3-C2 | Spend/price env vars missing from `.env.example` | Docs | +| P3-PERF | Hybrid RPC latency tail | Capacity workstream | +| P3-SIZE | Large modules (`rag.ts`, dashboard, document viewer) | Continue decomp when touching | Full address sketches: Remediation Wave I in the remediation plan. @@ -201,14 +201,14 @@ Full address sketches: Remediation Wave I in the remediation plan. ## 7. Checks run / not run -| Ran | Not run (need confirmation) | -| --- | --- | -| Specialist static audits | Live Supabase / OpenAI / Railway | -| Structure `verify:cheap` (reported green) | `check:drift`, `check:supabase-project` | +| Ran | Not run (need confirmation) | +| -------------------------------------------------- | --------------------------------------------------- | +| Specialist static audits | Live Supabase / OpenAI / Railway | +| Structure `verify:cheap` (reported green) | `check:drift`, `check:supabase-project` | | Focused Vitest (governance, RAG, ingestion suites) | `eval:retrieval:quality`, `eval:quality --rag-only` | -| `ci-change-scope` probes for scope holes | `verify:release`, `verify:ui` | -| | `check:production-readiness` with live secrets | -| | Security advisors / live A/B tenancy | +| `ci-change-scope` probes for scope holes | `verify:release`, `verify:ui` | +| | `check:production-readiness` with live secrets | +| | Security advisors / live A/B tenancy | --- @@ -235,17 +235,17 @@ Full address sketches: Remediation Wave I in the remediation plan. ## 9. Related references -| Doc | Role | -| --- | --- | -| [`audit-remediation-plan-2026-07-14.md`](audit-remediation-plan-2026-07-14.md) | How to fix, sequenced | -| [`operator-backlog.md`](operator-backlog.md) | Human/provider actions index | -| [`launch-operator-runbook.md`](launch-operator-runbook.md) | Sequenced launch commands | -| [`privacy-impact-assessment.md`](privacy-impact-assessment.md) | PIA register | -| [`openai-cross-border-basis.md`](openai-cross-border-basis.md) | APP 8 checklist | -| [`tenancy-defense-in-depth-review.md`](tenancy-defense-in-depth-review.md) | Tenancy defense notes | -| [`process-hardening.md`](process-hardening.md) | Process & known debts | -| [`codex-review-protocol.md`](codex-review-protocol.md) | Review severity / mutation rules | -| [`branch-review-ledger.md`](branch-review-ledger.md) | Prevent repeat audits | +| Doc | Role | +| ------------------------------------------------------------------------------ | -------------------------------- | +| [`audit-remediation-plan-2026-07-14.md`](audit-remediation-plan-2026-07-14.md) | How to fix, sequenced | +| [`operator-backlog.md`](operator-backlog.md) | Human/provider actions index | +| [`launch-operator-runbook.md`](launch-operator-runbook.md) | Sequenced launch commands | +| [`privacy-impact-assessment.md`](privacy-impact-assessment.md) | PIA register | +| [`openai-cross-border-basis.md`](openai-cross-border-basis.md) | APP 8 checklist | +| [`tenancy-defense-in-depth-review.md`](tenancy-defense-in-depth-review.md) | Tenancy defense notes | +| [`process-hardening.md`](process-hardening.md) | Process & known debts | +| [`codex-review-protocol.md`](codex-review-protocol.md) | Review severity / mutation rules | +| [`branch-review-ledger.md`](branch-review-ledger.md) | Prevent repeat audits | --- diff --git a/docs/audit-remediation-plan-2026-07-14.md b/docs/audit-remediation-plan-2026-07-14.md index c394aa165..eb1b44650 100644 --- a/docs/audit-remediation-plan-2026-07-14.md +++ b/docs/audit-remediation-plan-2026-07-14.md @@ -130,7 +130,7 @@ Suggested PR granularity (one theme per PR): ## Wave C — Code P1: anonymous catalog availability -### C1. Rate-limit anonymous public catalogs (M4) +### Wave C1. Rate-limit anonymous public catalogs (finding M4) | | | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -144,7 +144,7 @@ Suggested PR granularity (one theme per PR): ## Wave D — Security & tenancy P2 -### D1. Redact internal fields on authenticated public-document DTOs (S1 / S10 detail) +### Wave D1. Redact internal fields on authenticated public-document DTOs (findings S1 / S10 detail) | | | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -268,7 +268,7 @@ Suggested PR granularity (one theme per PR): | **Address** | For each `flake-ledger.json` entry: fix root cause **or** tag `@quarantine` with owner/date. Replace `waitForTimeout` with condition waits; stop relying on swallowed `networkidle` for readiness. | | **Prove** | Either ledger empty or every ledger title tagged/fixed; critical/regression green | -### F5. Align `.env.example` with safe defaults (C1) +### F5. Align `.env.example` with safe defaults (Finding C1) | | | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -438,8 +438,8 @@ After Waves B–H land (or are deferred with dated waiver): | --------------------------------------- | -------------------- | ----------------------------- | | Block real patients | B1, B2 | Legal + secret placement | | Block next release claim | B3, B4, J | Evidence missing | -| Fix before wide anon traffic | C1 | DoS / cost | -| Fix before multi-tenant trust expansion | D1, D2, D5 | Disclosure / regression class | +| Fix before wide anon traffic | Wave C1 | DoS / cost | +| Fix before multi-tenant trust expansion | Waves D1, D2, D5 | Disclosure / regression class | | Fix before next reindex / recovery | E1–E3 | Index correctness | | Fix before next UI merge wave | F1–F4, G1–G4 | Silent CI holes / a11y | | Hygiene | H1–H6, F5–F6, Wave I | Contracts & debt | @@ -459,4 +459,4 @@ After Waves B–H land (or are deferred with dated waiver): Append progress to the review ledger when a wave completes, and flip matching rows in [`operator-backlog.md`](operator-backlog.md). Prefer linking PRs back to issue IDs in this document -(`C1`, `E2`, …) in the PR body. +(`Wave C1`, `Wave E2`, …) in the PR body. diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 0077c7012..6500e48bc 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -490,7 +490,5 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD | 2026-07-14 | PR #655 / codex/release-blocker-remediation | 3ed3a7a2df37d7d15143ab7606e5748ac7ecca09 + reviewed follow-up diff | offline dose-route latency follow-up | The next isolated timeout was a short IM/PO agitation question receiving the same blanket ten-term AND expansion. Agitation dose/route retrieval now keeps only the dose and route signals present in the question; the exact case retrieves its expected source and five citations in 1.54 seconds without a model. All remaining RAG cases 23–44 passed individually, so no further deadline crash remains. | Focused clinical-search/retrieval Vitest 111/111; scoped ESLint; Prettier; `git diff --check`; live provider-free case 22 passed; live provider-free cases 23–44 passed individually. | | 2026-07-14 | PR #655 / codex/release-blocker-remediation | a3f3a89676015cd5f018c07e8c3ad9483f91cef6 + reviewed follow-up diff | offline adversarial-latency follow-up | The final blocking offline-quality failure was an adversarial secret-exfiltration query that correctly refused but first spent about 25 seconds in lexical retrieval. Adversarial manipulation now short-circuits at the search boundary before provider-client creation, cache access, classification, aliases, or Supabase work, and is never cached. | Focused Vitest 2/2; scoped ESLint; Prettier; full TypeScript; `git diff --check`; live provider-free adversarial case completed in 100 ms with 0 ms RPC time; `eval:quality:release:offline` passed with zero blocking failures and zero model, request-ID, token, cost, or generation-latency evidence. Flaky local browser/composite suites intentionally not repeated; hosted CI remains authoritative. | | 2026-07-14 | PR #666 / codex/release-blocker-remediation | 9b56eebe4b23ab783207445fb827c317c8d59be8 + reviewed follow-up diff | review-followup | One late P2 retrieval-contract gap was confirmed: the optimized agitation query retained IM/PO but could drop other already-supported amount, route, and frequency aliases. Medication evidence intent is now shared with retrieval selection, and focused agitation queries preserve requested numeric units, SC, SL, PRN, and frequency signals without restoring the broad ten-term expansion. | GitHub review-thread inspection; focused clinical-search/retrieval Vitest 112/112; scoped ESLint; Prettier; `git diff --check`. Hosted final-head TypeScript/build/CI and exact-head staging evidence remain required after push. | -| 2026-07-15 | PR #673 / cursor/audit-remediation-plan-0411 | d061beec41a906f5a3be761e719be0ecbd9279ea + review-followup | docs audit remediation plan review + CI | Hosted static-pr failed on Prettier for the new plan. Codex P1s: A2 scrub prove covered only -ag_queries (now all four process-hardening tables); B2 staging shared prod RAG_QUERY_HASH_SECRET (now staging-only). CodeRabbit: format, Wave I coverage for client-env.ts / mockup-named components (P3-STRUCT1/2), and -npm run prefixes for eval commands. | Focused Prettier on changed docs; git diff --check. Docs-only; no app code or provider checks. | -| 2026-07-14 | codex/formulation-specifier-separation-current | 25d7f733f + uncommitted fix | Specifiers/Formulation boundary bug hunt and remediation | Confirmed two P1 clinical-navigation/content boundary defects: the completed Formulation change deleted the distinct psychiatric Specifiers workspace and redirected every `/specifiers/*` route into Formulation; a later universal-search contract also labelled Formulation mechanism results as `specifiers`. Restored the independent Specifiers catalogue, detail, builder, compare, and map surfaces; registered both app modes and action sets; kept submitted searches in their owning standalone workspace; and split universal search into real `specifiers` and `formulation` domains with correct records, headings, mode ownership, and destinations. | Offline only: full Vitest 2,404 passed/1 skipped, TypeScript, full ESLint, focused Vitest 56/56, sitemap check, `git diff --check`, production-readiness READY with expected missing-env warnings, identity-checked server at `http://localhost:3384`, and repaired Chromium journeys for Specifiers desktop/mobile, Formulation mobile, and explicit route-family separation all passed. No provider-backed or live API checks run. | \ No newline at end of file +| 2026-07-15 | PR #673 / cursor/audit-remediation-plan-0411 | b26fd1a40c0a7fb119c84d69f0aa1532e3cab72c + reviewed follow-up diff | docs audit remediation plan review + CI | Fixed the hosted Prettier failure and all actionable review comments: preserved the real branch ref for the generic ledger parser, corrected `npm run` commands, scoped the no-P0/P1 verdict without obscuring the M4 code P1, disambiguated finding IDs from wave IDs, and aligned wave-to-ledger update instructions with the review protocol. No additional high-confidence defect remains in the changed docs. | GitHub unresolved-thread inventory; focused Prettier on all changed docs; `git diff --check`. Docs-only; no Supabase, OpenAI, or other live-service checks. | +| 2026-07-14 | codex/formulation-specifier-separation-current | 25d7f733f + uncommitted fix | Specifiers/Formulation boundary bug hunt and remediation | Confirmed two P1 clinical-navigation/content boundary defects: the completed Formulation change deleted the distinct psychiatric Specifiers workspace and redirected every `/specifiers/*` route into Formulation; a later universal-search contract also labelled Formulation mechanism results as `specifiers`. Restored the independent Specifiers catalogue, detail, builder, compare, and map surfaces; registered both app modes and action sets; kept submitted searches in their owning standalone workspace; and split universal search into real `specifiers` and `formulation` domains with correct records, headings, mode ownership, and destinations. | Offline only: full Vitest 2,404 passed/1 skipped, TypeScript, full ESLint, focused Vitest 56/56, sitemap check, `git diff --check`, production-readiness READY with expected missing-env warnings, identity-checked server at `http://localhost:3384`, and repaired Chromium journeys for Specifiers desktop/mobile, Formulation mobile, and explicit route-family separation all passed. No provider-backed or live API checks run. | From 07761b2a38ead108c9737aaddd0b798c9aecbd9c Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 15 Jul 2026 04:19:23 +0800 Subject: [PATCH 8/8] docs: require project preflight before release gates --- docs/audit-remediation-plan-2026-07-14.md | 12 ++++++------ docs/branch-review-ledger.md | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/audit-remediation-plan-2026-07-14.md b/docs/audit-remediation-plan-2026-07-14.md index eb1b44650..4013edf0c 100644 --- a/docs/audit-remediation-plan-2026-07-14.md +++ b/docs/audit-remediation-plan-2026-07-14.md @@ -101,12 +101,12 @@ Suggested PR granularity (one theme per PR): ### B3. Restore OpenAI quota and run release gates -| | | -| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Finding** | M3 — release gate + golden evals incomplete; prior quota exhaustion | -| **Owner** | OWNER:OPS | -| **Address** | Restore embedding/completions quota → run `npm run eval:retrieval:quality` (36/36) → `npm run eval:quality -- --rag-only` → `npm run verify:release` per [`launch-operator-runbook.md`](launch-operator-runbook.md) §2. | -| **Prove** | Paste summaries into release notes / backlog; canary path in B4 | +| | | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Finding** | M3 — release gate + golden evals incomplete; prior quota exhaustion | +| **Owner** | OWNER:OPS | +| **Address** | Complete the runbook §0 identity preflight with `npm run check:supabase-project`, then restore embedding/completions quota → run `npm run eval:retrieval:quality` (36/36) → `npm run eval:quality -- --rag-only` → `npm run verify:release` per [`launch-operator-runbook.md`](launch-operator-runbook.md) §2. | +| **Prove** | Paste summaries into release notes / backlog; canary path in B4 | ### B4. Eval Canary trust + staging soak diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 622b340e1..b4a094c44 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -490,7 +490,7 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD | 2026-07-14 | PR #655 / codex/release-blocker-remediation | 3ed3a7a2df37d7d15143ab7606e5748ac7ecca09 + reviewed follow-up diff | offline dose-route latency follow-up | The next isolated timeout was a short IM/PO agitation question receiving the same blanket ten-term AND expansion. Agitation dose/route retrieval now keeps only the dose and route signals present in the question; the exact case retrieves its expected source and five citations in 1.54 seconds without a model. All remaining RAG cases 23–44 passed individually, so no further deadline crash remains. | Focused clinical-search/retrieval Vitest 111/111; scoped ESLint; Prettier; `git diff --check`; live provider-free case 22 passed; live provider-free cases 23–44 passed individually. | | 2026-07-14 | PR #655 / codex/release-blocker-remediation | a3f3a89676015cd5f018c07e8c3ad9483f91cef6 + reviewed follow-up diff | offline adversarial-latency follow-up | The final blocking offline-quality failure was an adversarial secret-exfiltration query that correctly refused but first spent about 25 seconds in lexical retrieval. Adversarial manipulation now short-circuits at the search boundary before provider-client creation, cache access, classification, aliases, or Supabase work, and is never cached. | Focused Vitest 2/2; scoped ESLint; Prettier; full TypeScript; `git diff --check`; live provider-free adversarial case completed in 100 ms with 0 ms RPC time; `eval:quality:release:offline` passed with zero blocking failures and zero model, request-ID, token, cost, or generation-latency evidence. Flaky local browser/composite suites intentionally not repeated; hosted CI remains authoritative. | | 2026-07-14 | PR #666 / codex/release-blocker-remediation | 9b56eebe4b23ab783207445fb827c317c8d59be8 + reviewed follow-up diff | review-followup | One late P2 retrieval-contract gap was confirmed: the optimized agitation query retained IM/PO but could drop other already-supported amount, route, and frequency aliases. Medication evidence intent is now shared with retrieval selection, and focused agitation queries preserve requested numeric units, SC, SL, PRN, and frequency signals without restoring the broad ten-term expansion. | GitHub review-thread inspection; focused clinical-search/retrieval Vitest 112/112; scoped ESLint; Prettier; `git diff --check`. Hosted final-head TypeScript/build/CI and exact-head staging evidence remain required after push. | -| 2026-07-15 | PR #673 / cursor/audit-remediation-plan-0411 | b26fd1a40c0a7fb119c84d69f0aa1532e3cab72c + reviewed follow-up diff | docs audit remediation plan review + CI | Fixed the hosted Prettier failure and all actionable review comments: preserved the real branch ref for the generic ledger parser, corrected `npm run` commands, scoped the no-P0/P1 verdict without obscuring the M4 code P1, disambiguated finding IDs from wave IDs, and aligned wave-to-ledger update instructions with the review protocol. No additional high-confidence defect remains in the changed docs. | GitHub unresolved-thread inventory; focused Prettier on all changed docs; `git diff --check`. Docs-only; no Supabase, OpenAI, or other live-service checks. | +| 2026-07-15 | PR #673 / cursor/audit-remediation-plan-0411 | 03fe72683d83606b73c951914278eabe897fc395 + reviewed follow-up diff | docs audit remediation plan review + CI | Fixed the hosted Prettier failure and all actionable review comments: preserved the real branch ref for the generic ledger parser, corrected `npm run` commands, scoped the no-P0/P1 verdict without obscuring the M4 code P1, disambiguated finding IDs from wave IDs, aligned wave-to-ledger update instructions with the review protocol, and made B3 require the Supabase project-identity preflight before provider-backed release gates. No additional high-confidence defect remains in the changed docs. | GitHub unresolved-thread inventory; focused Prettier on all changed docs; docs link/script reference checks; `git diff --check`. Docs-only; no Supabase, OpenAI, or other live-service checks. | | 2026-07-14 | codex/formulation-specifier-separation-current | 25d7f733f + uncommitted fix | Specifiers/Formulation boundary bug hunt and remediation | Confirmed two P1 clinical-navigation/content boundary defects: the completed Formulation change deleted the distinct psychiatric Specifiers workspace and redirected every `/specifiers/*` route into Formulation; a later universal-search contract also labelled Formulation mechanism results as `specifiers`. Restored the independent Specifiers catalogue, detail, builder, compare, and map surfaces; registered both app modes and action sets; kept submitted searches in their owning standalone workspace; and split universal search into real `specifiers` and `formulation` domains with correct records, headings, mode ownership, and destinations. | Offline only: full Vitest 2,404 passed/1 skipped, TypeScript, full ESLint, focused Vitest 56/56, sitemap check, `git diff --check`, production-readiness READY with expected missing-env warnings, identity-checked server at `http://localhost:3384`, and repaired Chromium journeys for Specifiers desktop/mobile, Formulation mobile, and explicit route-family separation all passed. No provider-backed or live API checks run. | | 2026-07-14 | claude/specifiers-v2-design-r55baf | 343f4ee4e89844e6a668910958ce1e3f119c128e | branch-cleanup | Retained: open PR #656 (full DSM-5-TR specifier catalog; +19k, novel data/loaders not on main). | Provider-backed GitHub PR inventory via MCP (400 PRs #264-#674, all merged=false); local read-only git: rewritten-main-aware cherry-pick + two-dot tree + branch-only-file novelty sweep vs origin/main e75fad90; adversarial re-check. No remote mutation attempted. No OpenAI/Supabase/live-eval checks. | | 2026-07-14 | codex/formulation-specifier-separation-current | 17c9ea7383f5b463ec648317cd170c24b8e7c584 | branch-cleanup | Retained: open PR #672 (Specifiers/Formulation separation residual; core already landed on main via #674). | Provider-backed GitHub PR inventory via MCP (400 PRs #264-#674, all merged=false); local read-only git: rewritten-main-aware cherry-pick + two-dot tree + branch-only-file novelty sweep vs origin/main e75fad90; adversarial re-check. No remote mutation attempted. No OpenAI/Supabase/live-eval checks. |