diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 7ee6ef98c9..259bdd75f7 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -883,7 +883,7 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-11 | codex/answer-loading-ui-20260811 | 6758f8156f9d1b3e893981dfd7a1f6563aa90da0 | answer creation loading UI | No high-confidence findings | UI 3 passed; unit 8 passed; lint, typecheck, build, design-system and offline RAG passed; full suite 6022 passed with 16 unchanged baseline failures | | 2026-08-11 | claude/codex-m4c-retire-shadow-nliak3 | 448a0d084c4cd2cda6153dd7f03dcb67c43a8df0 | DS Track A2 (#261): retire --shadow-focus; composer focus onto sanctioned outline; contract guard; baseline ratchet; design-system docs + ledger | Approved — PR #1807. Token deleted in both themes; .chat-composer-shell-delta:focus-within uses outline 2px var(--focus) at offset 2px and no longer overrides box-shadow. Reach premise corrected: 0 of 37 production routes render the class (only /mockups/calculators-search). legacyShadowAliases 127->125, globals.css pin 3->1. | check:design-system-contract PASS; design-token-contract.test.ts PASS + mutation-verified both ways; verify:pr-local PASS except pre-existing tests/pr-handoff-stop.test.ts failure baselined on untouched base e8b61d8; build PASS; check:rag:fixtures PASS (36 cases); Chromium look both themes on the mockup route (inspection only, rev 1194 vs pinned 1234 #255); verify:ui/verify:phone-chrome NOT run — delegated to CI | | 2026-08-11 | codex/answer-ecg-animation-20260811 | 12279a8309c225aa957ef1e65afc37545a0ce04c | answer ECG progress variants | No high-confidence findings; physical Safari/PWA remains residual acceptance | design contract, typecheck, focused unit 8/8, trace token 33/33, Chromium 4/4, offline RAG 574/574; full suite baseline/platform failures | -| 2026-08-11 | claude/filter-popup-design-mockups-x6sbjv | 7b64f2559741a9f353adcf939745831e0daff7db | services filter sheet redesign mockups (3 directions, desktop+phone) | PR #1828 opened; design-scratch route only, no production behaviour change | verify:pr-local (1 pre-existing root-uid test failure, reproduced on origin/main 046feb3), build, check:rag:fixtures, check:bundle-budget both baselines within tolerance, 320px 0px overflow | +| 2026-08-11 | codex/chat-services-flow-redesign-20260812 | 6f44b92defb91bcd77509bf10337b428be37619c | Services home, results, shortlist, comparison, and referral detail redesign | No findings; changed-area UI, phone contracts, focused unit, build, and RAG fixtures passed; PR-local Windows baseline limitations documented. | 78 focused tests passed post-merge; 185 changed-browser tests; 129 phone contracts; 7 phone-scroll tests; build and RAG fixtures passed | | 2026-08-11 | work | 6dcd695076d630d16aae594577763e8004361893 | Codex Cloud setup and local parity | P2 fixed: cache-friendly locked Cloud npm install; parity limitations documented | check:codex-cloud; codex-cloud-setup 24/24; full suite 6059 pass, 7 unrelated timeout/state failures | | 2026-08-11 | 1822 | 4fab267f52b72992745e1d2e6975fb4847af447a | review-and-fix | clean | Build pass; Static PR checks pass; Change scope pass; PR mergeability pass; PR policy pass; Safety and config checks pass; Semgrep pass; Semgrep ingestion gate pass; Gitleaks pass; GitGuardian pass; Unit coverage pending; Production UI (1) pass; Production UI (2) pass; Production UI critical pending; Production UI (3) pending; Lighthouse budget pass; PR required pending | | 2026-08-11 | 1822 | 4fab267f52b72992745e1d2e6975fb4847af447a | review-and-fix (supersedes 2026-08-11) | clean | Build pass; Static PR checks pass; Change scope pass; PR mergeability pass; PR policy pass; Safety and config checks pass; Semgrep pass; Semgrep ingestion gate pass; Gitleaks pass; GitGuardian pass; Unit coverage pass; Production UI (1) pass; Production UI (2) pass; Production UI (3) pass; Production UI critical pass; Lighthouse budget pass; PR required pass | @@ -891,13 +891,17 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-11 | 1821 | d76e90547dbdb104256b66a508c15c74302002fc | review-and-fix | dispositioned | PR policy:success; PR mergeability:success; Gitleaks:success; Semgrep:success; Semgrep ingestion gate:success; Safety and config checks:success; Build:success; Production UI critical:success; Production UI (1):success; Production UI (2):success; Production UI (3):failure test not reproduced outside this PR; PR required:failure | | 2026-08-11 | work | 45fd05c8c3947835c0368666ff576c7a38b33ee4 | mobile evidence sheet UX, accessibility, and feedback logic | fixed unexplained claim marker, excess panel reserve, unclear purpose and feedback copy; no remaining high-confidence defects | focused DOM 7/7; Chromium evidence journey 1/1; offline RAG 23 suites/574 tests | | 2026-08-11 | 1820 | 897ff11a4cdb13ae1c01f5eb149007847028f5aa | review-and-fix | fixed | Semgrep:IN_PROGRESS, Gitleaks:IN_PROGRESS, Semgrep ingestion gate:IN_PROGRESS, Static PR checks:QUEUED, Safety and config checks:QUEUED, Unit coverage:QUEUED, Build:QUEUED, Production UI critical:QUEUED, Lighthouse budget:QUEUED | -| 2026-08-12 | claude/filter-popup-design-mockups-x6sbjv | 5e41d164e30e8f5a74b255fbeafd34123385dbb9 | services filter: round-two options study (stop-the-bleed / recommended / presets-evicted) | Pushed to PR #1828; merged babysit fixes to round-one facet semantics; design-scratch only | verify:pr-local (1 pre-existing root-uid failure only), build, check:rag:fixtures, bundle-budget mockups 286.8 KiB within 25% tolerance, counts re-verified vs snapshot, 320px 0px overflow | -| 2026-08-11 | codex/chat-services-flow-redesign-20260812 | 6f44b92defb91bcd77509bf10337b428be37619c | Services home, results, shortlist, comparison, and referral detail redesign | No findings; changed-area UI, phone contracts, focused unit, build, and RAG fixtures passed; PR-local Windows baseline limitations documented. | 78 focused tests passed post-merge; 185 changed-browser tests; 129 phone contracts; 7 phone-scroll tests; build and RAG fixtures passed | | 2026-08-11 | claude/spacing-icon-design-review-rxwh28 | f8701a524f0eb22decc64ce1f626bdafe91751af | mode-home hero spacing rhythm + icon scale (PR #1815) | shipped: group copy reserve banded to measured wrap points, continuous hero medallion, phone composer glyph 1.1rem->icon-lg, surface glyphs onto size-icon-*, privacy link bottom-only negative margin (fixes 8px tap overhang on the APP-5 sentence), composer phone reserve 7.625->6.625rem | test 6043 passed/1 pre-existing root-perm failure; lint; typecheck; build; check:icon-scale; check:type-scale; check:design-system-contract; check:rag:fixtures; check:bundle-budget; prettier --check .; verify:ui NOT run (Playwright r1234 vs image r1194, #255 - delegate to CI Production UI) | | 2026-08-11 | 1815 | be7461ef1f66357999995acefbeecaf95268e481 | unblock | local-build-pass | MergeTreeClean,UnitCoverage,StaticPRChecks,ContainerImages | | 2026-08-11 | claude/spacing-icon-design-review-rxwh28 | 455bc198c077860fb1f830670a5fa9c1de08da52 | pr-1815 heavy review-and-fix | remote already merged main (shadow-tight Switch kept); cherry-picked privacy -mb-4 reclaim + calculators dock cancel; removed duplicate UniversalSearchAlsoMatches; rail-aware section-sheet focus restore; dispositioned CodeRabbit docs/ledger/gates nits and outdated Sentry skeleton gap | verify:cheap PASS prior tip; verify:pr-local PASS prior tip; vitest privacy+in-page-nav 28 passed on cherry-pick; merge-tree clean vs origin/main | | 2026-08-11 | claude/spacing-icon-design-review-rxwh28 | 5b96281ee7da817d5ce7f1102004ebe6f861b920 | pr-1815 heavy review-and-fix | remote already merged main (shadow-tight Switch kept); cherry-picked privacy -mb-4 reclaim + calculators dock cancel; removed duplicate UniversalSearchAlsoMatches; rail-aware section-sheet focus restore; dispositioned CodeRabbit docs/ledger/gates nits and outdated Sentry skeleton gap | verify:cheap PASS prior tip; verify:pr-local PASS prior tip; vitest privacy+in-page-nav 28 passed on cherry-pick; merge-tree clean vs origin/main | | 2026-08-12 | PR #1815 / claude/spacing-icon-design-review-rxwh28 | 9f266210f02081be54d407c70a85f52fed436128 | babysit | no remaining actionable findings; one pre-existing thread resolved as no-change (Dockerfile.worker follow-up needed) | required checks: Gitleaks PR policy PR required (all pass); targeted vitest passed: tests/document-frame-contract.test.ts + tests/in-page-nav-header.dom.test.tsx | | 2026-08-12 | 1815 | 27ce96e1755055ceee2eeae02d6efdf11259fcde | babysit | fixed | Unit coverage: targeted vitest passed: tests/shared-home-empty-state.dom.test.tsx (17 passed). PR required still blocked on pre-existing check failure at old remote head before sync. | -| 2026-08-12 | claude/rag-canary-test-review-seprbt | bcf357a96fde74d39fc4726ffabb5079a744ef28 | eval-canary review: workflow, compare tooling, snapshot builder, alias tiering, rag-behaviour docs | PR #1843 opened; no retrieval behaviour change; snapshot refresh handed off as /issues #304 | verify:pr-local (green except env-only #296), eval:rag:offline 574/574, focused suites 40/40 | +| 2026-08-12 | claude/design-issues-triage-wnr7k9 | a6bfc6f2707975d9b9c649843e083965c80afb9c | Tier 1 design-issue re-verification: archive #171/#172/#174/#181/#273/#274/#302, correct #293 | docs-only; six rows verified delivered on main, min-h-tap finding refuted as deliberate sm: step-down | verify:pr-local (10/10 green); check:outstanding-issues 138 open/163 archived | +| 2026-08-12 | claude/design-issues-triage-wnr7k9 | 970d39bc7023823d3283c660df090659a2f07aec | Full outstanding-issues ledger sweep: 47 rows individually verified against merged main | 19 archived (delivered or duplicate), 10 re-scoped with re-measured evidence, 1 refuted (#293), 4 machine-local rows annotated do-not-close-from-cloud; 98 rows bucketed by blocker, not individually verified | verify:pr-local 10/10 green; check:outstanding-issues 126 open/175 archived, no ids deleted from base | +| 2026-08-11 | claude/filter-popup-design-mockups-x6sbjv | 7b64f2559741a9f353adcf939745831e0daff7db | services filter sheet redesign mockups (3 directions, desktop+phone) | PR #1828 opened; design-scratch route only, no production behaviour change | verify:pr-local (1 pre-existing root-uid test failure, reproduced on origin/main 046feb3), build, check:rag:fixtures, check:bundle-budget both baselines within tolerance, 320px 0px overflow | +| 2026-08-12 | claude/filter-popup-design-mockups-x6sbjv | 5e41d164e30e8f5a74b255fbeafd34123385dbb9 | services filter: round-two options study (stop-the-bleed / recommended / presets-evicted) | Pushed to PR #1828; merged babysit fixes to round-one facet semantics; design-scratch only | verify:pr-local (1 pre-existing root-uid failure only), build, check:rag:fixtures, bundle-budget mockups 286.8 KiB within 25% tolerance, counts re-verified vs snapshot, 320px 0px overflow | | 2026-08-12 | codex/pr-workflow-safety-230-296 | bc0a491fdf4146775629f9b2b03e2a2cc61bd7cb | pr-1830 unblock | unblocked: merged origin/main (outstanding-issues conflict), PR body RAG impact + governance, resolved Copilot thread; merge-tree clean; required CI in progress | check:outstanding-issues pass; evaluatePullRequestPolicy ok; merge-tree clean; PR policy/mergeability/Change scope in progress | +| 2026-08-12 | claude/design-issues-triage-wnr7k9 | 2553b64b2342b0ef2ebef0afde152e76c1252496 | Ledger sweep round 2: open-PR cross-check plus clinical/answer-surface verification | 24 rows flagged IN FLIGHT against 7 open PRs (none had said so); 3 answer-surface rows archived (#166 #208 #216); #250 wave plan re-scoped; main-merge conflict resolved preserving both sides, 23 branch changes re-applied via the writer | verify:pr-local 10/10 green; check:outstanding-issues 121 open/180 archived, no ids deleted from base | +| 2026-08-12 | claude/rag-canary-test-review-seprbt | bcf357a96fde74d39fc4726ffabb5079a744ef28 | eval-canary review: workflow, compare tooling, snapshot builder, alias tiering, rag-behaviour docs | PR #1843 opened; no retrieval behaviour change; snapshot refresh handed off as /issues #304 | verify:pr-local (green except env-only #296), eval:rag:offline 574/574, focused suites 40/40 | +| 2026-08-12 | claude/design-issues-triage-wnr7k9 | 586012639565e4d3306b44361ebc5a3bdb3024ad | Land PR #1838 ledger sweep; close #147 mobile CLS by measurement | Merge resolved as union (main renumbered #302/#303 to #306/#307 — not lost, correcting an earlier claim); #306/#307 archived as already-delivered. #147 archived on two identical offline Lighthouse runs: mobile CLS 0.035/0.000/0.013/0.081/0.000, all under 0.1, cause fixed by PR #1616 not this session. #118 updated (browser drift 141-vs-151, wider than recorded); new #308 for desktop /documents/search CLS 0.119 | verify:pr-local 10/10 green; check:outstanding-issues 121 open/185 archived; verify:lighthouse x2 (gate ungraded on browser drift, measurements valid) | diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index a7742e3c7d..25d80d3331 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -66,104 +66,88 @@ removed after current-main verification; it is not missing recommended work. | 11 | `#056` | A2 | Operator — Supabase/Railway + Specialist | Next approved staging schema window | 2–4 hours | Reconcile the existing healthy, empty staging tier's 24-migration history gap using the exact repository migration chain, then re-run indexing, health, identity and data-boundary proof. Never recreate it or copy production clinical documents. | | 12 | `#057` | A2 | High — release/SRE + Operator | After `#056` | 2–4 hours plus soak | Run documented staging soak and rollback against an exact candidate. Retain latency/error/rollback evidence; stop on unsafe data, identity mismatch, or unowned rollback. | | 13 | `#011` | A3 | Operator — Supabase capacity | Immediately before first compute scale-up | 30–60 min plus observation | Switch Auth to percentage allocation, record before/after, and run approved advisor/health checks. Stop if no scale-up is planned. | -| 14 | `#147` | A3 | High — frontend layout/performance | Attribution done; the fix is next and is phone-chrome governed | 2–4 hours | Stop `usePhoneOverlayChromeReserve` publishing a stale 200px reserve it revises to 72px 15-60ms later — that round trip moves all main content down 128px and back, and is 100% of `/documents/search`'s CLS and ~75% of `/dsm`'s. Defer the first publish until the stack settles, or let the ResizeObserver be the only writer and trust the CSS seed (which is already correct) until it fires. Phone-chrome surface: read `docs/search-chrome-behaviour.md`, run `npm run verify:phone-chrome`, and produce a before/after CLS pair from the offline harness. **Stop:** do not re-dispatch the live workflow, do not read LCP from local runs, and do not change the CSS seed — it is not the cause. | -| 15 | `#117` | A3 | High — frontend/perf + product | After per-field card-vs-search decision | 0.5–1 day once fields decided | Cut `/therapy-compass` mobile LCP by trimming or deferring the 690 KB `therapies-index.json` prose payload. Confirm each long-form field is card-rendered, search-matched, or neither before dropping it. Gate: `check:therapy-data-index`, therapy Playwright journeys, `verify:lighthouse`. **Stop:** do not strip fields without that confirmation. | -| 16 | `#118` | A3 | High — CI/visual/perf gates | After `#147` and `#117` (do not bake current breaches) | 2–4 hours | Commit the CI-uploaded visual baselines under the platform-scoped snapshot path, run `check:lighthouse-budget -- --update` on a known-good build, then flip `enforce` so `visual-baseline` and `lighthouse-budget` block. **Stop:** never commit baselines from a developer machine; never enforce while `#147`/`#117` still breach. | -| 17 | `#033` | A3 | Specialist — prompt/source governance | After `#022` and explicit evaluation approval | 1–2 days plus approved eval | Design unknown-vs-adverse metadata wording and prompt tests. Require no supported-grounding drop and zero citation failures; stop on broad over-caveating or degradation. | -| 18 | `#013`, `#016` | A3 | High — bundling/runtime performance | After `#147`/`#117` or equivalent evidence | 0.5–2 days/route | Optimize only a production route with measured payload/render/motion harm. Require material gain plus focused, `verify:cheap`, and browser evidence; stop on small gain. | -| 19 | `#035` | A3 | Specialist — evidence rules | After a demonstrated missed conflict | 0.5–1 day design; code separate | Define a clinically reviewed conflict class with positive and negative fixtures. Stop if no bounded class can be shown; behavior change requires protected review. | -| 20 | `#027` | Optional | Operator — SRE/provider | When an owned external alert path is wanted | 1–2 hours | Decide vendor/cost/privacy/owner; if accepted, prove one non-PHI outage and recovery alert. Stop when no responder owns it. | -| 21 | `#039` | Optional | High — frontend architecture | During a concrete catalogue-toolbar project | 0.5–1 day inventory; 1–3 days code | Converge only repeated toolbar behavior without flattening search semantics. Stop when there is no bounded implementation target. | -| 22 | `#079` | Optional | High — repository hygiene | In explicitly scheduled batches | 30–60 minutes per batch | Disposition at most ten retained worktrees per pass using owner, PR, review-ledger, ancestry, and patch evidence. Preserve every dirty, active, secret-bearing, post-freeze, or ambiguous worktree and stop rather than broad-cleaning. | -| 23 | `#149` | A2 | High — install/gate integrity | Next verify-tooling pass | 1–3 hours | Widen `check:installed-lock-parity` beyond the seven top-level packages so transitive drift (e.g. `brace-expansion` CVE patch) fails closed. Prove with a fixture where only a nested dependency mismatches. **Stop:** do not weaken SessionStart skip-install behaviour without the wider check. | -| 24 | `#086` | A3 | High — repository structure + Specialist | On explicit go-ahead for X3; later packages own their gates | 1 PR per work order | Ship remaining maturity backlog (X3 rag.ts; X7 src/lib reorg; X6 coverage floors; X5 ACL consolidation; L1 one-shot archive; M1 host hardening) as verified draft PRs from `docs/maturity-backlog-workorders.md`. L4 ledger rotation shipped in #1418. Start with X3 after go-ahead; stop before RAG edits without the flag or X5 without live-DB approval. | -| 25 | `#098` | A3 | High — test infrastructure | Before `#099` or `#101`; it is their enabler | 2–4 hours | Generalise the answer-route preamble guard into a counting-proxy round-trip budget harness over the existing offline fixtures. Must enforce admission-before-scope, never the reverse. No providers, no DB. Stop if it would require live credentials. | -| 26 | `#102` | A3 | Operator — Supabase + Specialist | Next approved index window, after the ordering question is settled | 1–2 hours plus apply | Author the migration (operator SQL alone never reaches staging/DR/local replay), then apply → mirror `schema.sql` → regenerate drift manifest → register `required_indexes`. **Stop:** the RAG-path index is canary-gated, and ordering `fetchDocumentTitleAliasRows`'s unordered `.limit(12)` does not lift that — an imposed order can select a different twelve, so it is a second canary-gated change, not a way out of the first. The byte-identical claim was retracted. | -| 27 | `#099` | A3 | Specialist — answer path | After `#098` | Half a day per sub-item | Remaining fixed per-request round trips: the 8 `setCachedSearch` deferrals (abort semantics + mutation window), the anonymous subject+global limiter pair (needs a new atomic RPC first), and proxy→route identity duplication. Stop before hand-authoring locking SQL. | -| 28 | `#162` | A3 | High — frontend/UI | When starting the mode search redesign package | 0.5–1.5 days | Redesign `/tools?q=` as Compact Results Instrument (direction A): query-as-H1, one composer, dense tool rows, demote cross-mode cards, remove success-green filter banner and home hero on results. Comps in `public/mockups/mode-page-redesign-2026-07/tools-search/`. Verify phone+desktop chrome ownership and `verify:phone-chrome` / focused UI. Stop if scope expands into Tools home redesign without an explicit ask. | -| 29 | `#163` | A3 | High — frontend/UI | After or with `#162` | 0.5–1.5 days | Redesign `/services?q=` as Progressive Referral Workflow (direction B): H1 = query (not match count), progressive shortlist/compare (no always-on decision panel or giant step rail). Comps in `public/mockups/mode-page-redesign-2026-07/services-search/`. Verify referral shortlist still works; stop before changing Services home ModeHome. | -| 30 | `#164` | A3 | High — frontend/UI | Product confirmed Favourites is hybrid dashboard+search (no ModeHome) | 1–2 days | Redesign Favourites as one dashboard + search page: recommended Search-Led Workspace (direction B) — persistent search, sets as chips, Continue + recent + table on empty query, in-place filter on typed query. Comps in `public/mockups/mode-page-redesign-2026-07/favourites-hybrid/`. Do not reintroduce ModeHome for Favourites. Verify desktop+phone; stop before splitting into separate ModeHome routes. | -| 31 | `#202` | A3 | High — agent process | Next issues-skill touch | 30–60 min | Require recommendation/`/issues` answers to revalidate against `origin/main`'s ledger (or state checkout lag). Prevents re-proposing closed work from stale worktrees. | -| 32 | `#186` | A3 | Specialist — RAG ledger accuracy | Before any `#101` canary | 30–60 min | Rewrite open `#101` to credit PR 1474 hydration parallelisation and list only remaining canary-gated candidates. | -| 33 | `#187` | Optional | High — ledger hygiene | When writing the durable notes | 30–60 min | After one-line notes land for `#151`/`#154`, archive those process-lesson rows so the open table stays actionable. | -| 34 | `#090` | A3 | High — eslint toolchain | When ESLint 10 plugin peers are compatible | blocked; revisit monthly | Upgrade the eslint ecosystem to clear remaining dev-scoped high advisories — full `npm audit` reports zero high advisories from the eslint toolchain. | -| 35 | `#100` | A3 | Specialist — answer streaming | After offline Phase 0/1 design proof | provider-gated rollout | Buffered answer generation has no incremental verified delivery — [`verified-answer-incremental-delivery-design.md`](verified-answer-incremental-delivery-design.md) records the clinical-governance decision and staged co… | -| 36 | `#150` | Optional | Operator — review tooling | Next CodeRabbit billing/policy decision | 30–60 min decision | CodeRabbit reviewed none of a full day's PRs; spending cap reached — the repo's second automated reviewer is either funded or acknowledged as absent, rather than appearing to review while skipping. | -| 37 | `#152` | A2 | High — worktree hygiene | Next cleanup batch with #079 | 1–2 hours | Uncommitted work sits in worktrees whose branches are already merged — work that exists in no branch and no PR is either committed or knowingly discarded, not lost to a disk reclaim. | -| 38 | `#155` | A2 | High — agent process | Standing rule; next multi-agent session | process change | Several agent sessions edit the same branch and ledger concurrently — concurrent sessions stop silently undoing each other on shared `claude/*` branches and on this file. | -| 39 | `#159` | A3 | High — test hygiene | Next test-infra pass | 1–2 hours | Lists naming test files are duplicated, and the stale copy fails by running nothing — no gate, plan or config names a set of test files in a second place without being derived from the filesystem or asserted against it. | -| 40 | `#165` | A2 | High — clinical UI | Next answer-home UX pass | 0.5–1 day | Adopt a consolidated answer-home notice block — the studies exist, nothing adopts them — the answer hero states its safety obligation, its scope, and its verification requirement as one block in one voice. | -| 41 | `#166` | A2 | High — clinical safety UI | With #165 or next clinical chrome pass | 2–4 hours | Answer mode ships no verify-before-use caveat; every other clinical mode does — the surface that actually generates prose from retrieved sources says so, and says it must be checked. | -| 42 | `#168` | A3 | High — ledger architecture | With #156 / id-scheme redesign | design first | Sequential issue ids force every concurrent append to conflict — two sessions can append to this ledger at the same time without conflicting. | -| 43 | `#169` | A3 | High — git hygiene | Next branch cleanup batch | 1–2 hours | Local branches carry work that exists on no remote — committed work is not lost when a machine or worktree is reclaimed. | -| 44 | `#170` | A2 | High — phone UI | Next documents/filter phone pass | 0.5–1 day | Documents and therapy already have page-owned phone filter sheets; remaining modes still use inline controls — shared-band Filter+Sheet adoption without regressing those two or sheetless Sort. | -| 45 | `#171` | A2 | High — documents UI | With #170 / filter consolidation | 0.5–1.5 days | Documents mode has four overlapping filtering surfaces, two of them the same job — one Filter control opening one panel, so a reader learns filtering once. | -| 46 | `#175` | A2 | Operator — clinical data + Standard | Next therapy catalogue curation window | 2–4 hours | Therapy modality is now null on all 205 records and needs curation or removal — the Therapy detail and recommend screens either show a curated modality or stop carrying the field at all. | -| 47 | `#178` | A3 | High — PR policy | Next pr-policy change | 1–2 hours | pr-policy does not flag operational risk bundled with clinical or UI risk — a PR that mixes operational-risk paths with clinical or UI risk is called out before it merges, because squash-merging that mix destroys per-it… | -| 48 | `#189` | A2 | Specialist — search/RAG budgets | After #098 route residual; before collapsing RPCs | 2–4 hours + canary if behaviour | Pin /api/search route-level round trips and disposition the x3 text RPC probes — a counting-proxy budget drives `POST` `/api/search` (auth/ratelimit/scope/enrichment/telemetry), and the retrieval-core finding that `matc… | -| 49 | `#036` | Optional | Specialist — privacy/schema | When visibility model is redesigned | design + migration | No explicit `is_public` visibility flag on documents — Public-corpus visibility is implicit: `owner_id IS NULL` on an `indexed` document (`resolveSearchScope`). The `metadata.public_corpus` marker is written by the prom… | -| 50 | `#101` | A3 | Specialist — RAG/retrieval | After #186 update + canary approval | canary-gated | Canary-gated retrieval parallelisation candidates — independent retrieval stages stop running serially, proven by a live canary pair. Candidates: metadata/memory/visual hydration triples repeated on four branches (`rag.… | -| 51 | `#142` | Optional | High — docs hygiene | Next docs filing pass | 1–2 hours | Four loose dated docs need source and migration edits before they can be filed — every dated point-in-time doc lives in `docs/audit/` or `docs/archive/` as `docs/README.md` requires, not loose at the `docs/` top level. | -| 52 | `#151` | Optional | Operator — GitHub PAT | When writing the durable note (#187) | 15–30 min | `gh pr checks` cannot read CI, but the Actions API can — nobody concludes CI is unverifiable when it is merely reached through a different endpoint. | -| 53 | `#154` | Optional | High — agent process | When writing the durable note (#187) | 15–30 min | Row ids are not stable identifiers for "did my change land" — an agent confirms work reached `main` by content, never by id, title or PR state. | -| 54 | `#156` | A3 | High — ledger architecture | With #168 id-scheme work | design first | Outstanding-issues ids are still allocated read-modify-write, and Update-branch corrupts the merge — two branches cannot silently claim the same outstanding-issues id, and no merge path can commit a file where they have. | -| 55 | `#172` | A3 | High — documents UI | With #171 filter consolidation | 1–2 hours | `Sources` sits in the results bar but is navigation, not a filter — the results bar holds only controls that act on the current results. | -| 56 | `#174` | A3 | High — search facets | When facet UX is redesigned | 0.5–1 day | Facets AND within a group, so two values from one group almost always return nothing — a decision on record, either way. | -| 57 | `#177` | A3 | High — therapy catalogue build | Next therapy-index build change | 1–2 hours | Therapy catalogue aliases duplicate 2.53 MB of bytes instead of pointing at the hashed file — the unversioned catalogue aliases stop costing a second copy of every payload in the repo and the image. | -| 58 | `#179` | A3 | High — therapy catalogue build | With #177/#180 | 1–2 hours | The full therapy catalogue silently switched from minified to pretty-printed — the full catalogue's on-disk format is a decision someone made, not a side effect. | -| 59 | `#180` | A3 | High — therapy catalogue build | With #177/#179 | 1–2 hours | build-therapies-index now overwrites its own source input — the therapy catalogue generator has a source it does not also destroy. | -| 60 | `#181` | Optional | High — documents UI clarification | When updating #171 | 15–30 min | Correction to `#171`: source-type does NOT duplicate the `Document type` facet group — `#171` states that the documents source-type control "duplicates the facet group already named `Document type`". That is wrong, and … | -| 61 | `#188` | A3 | Operator — DR/SRE | After any schema restore drill, or next DR review | checklist-owned | Document and track disaster-recovery re-creation checklist as ledger work — the five DR items that do not survive a schema restore are tracked with owners and verify steps, not only in `docs/operator-backlog.md`. | -| 62 | `#190` | A3 | Specialist — RAG structure | On explicit X3 go-ahead | 1 PR per extraction unit | X3: Finish rag.ts monolith decomposition — `src/lib/rag/rag.ts` is decomposed into focused modules per `docs/maturity-backlog-workorders.md` X3, with existing offline RAG contracts green. | -| 63 | `#191` | A3 | Operator — DB + Specialist | Approved live-DB window only | provider-gated | X5: ACL-migration consolidation (provider-gated) — ACL-related migrations are consolidated per maturity work-order X5 without weakening owner-scope/RLS. | -| 64 | `#192` | A3 | High — test coverage | Next coverage-floor pass | 0.5–1 day | X6: Raise clinical/retrieval/answer coverage floors — coverage floors for clinical, retrieval, and answer domains meet the maturity X6 targets with CI enforcing them. | -| 65 | `#193` | A3 | High — src/lib structure | After/with X3 non-protected clusters | 1 PR per cluster | X7: Complete the remaining src/lib domain-directory reorg — remaining `src/lib` clusters sit in their domain directories per X7 follow-on to X2. | -| 66 | `#194` | A3 | High — scripts/docs hygiene | Next scripts archive pass | 1–2 hours | L1: Archive retired backfill one-shots and dead ci-change-scope token — retired `backfill:*` one-shots and the dead `ci-change-scope` token are archived/removed with docs/script index updated. | -| 67 | `#195` | A3 | Operator — GitHub maintainer | Maintainer UI window | 30–60 min | M1: Repo-host hardening (branch protection and required checks) — GitHub branch-protection rulesets and required checks match audit §8 / maturity M1. | -| 68 | `#196` | A3 | Operator — DR/SRE | After schema restore drill | 1–2 hours | DR: Re-create pg_cron schedules after schema restore — ingestion/retention and related pg_cron schedules exist on the target DB after any schema restore. | -| 69 | `#197` | A3 | Operator — DR/SRE | After schema restore drill | 30–60 min | DR: Re-add Vault secrets including cron_ingestion_jwt — required Vault secrets (at least `cron_ingestion_jwt`) are present after schema restore. | -| 70 | `#198` | A3 | Operator — DR/SRE | After schema restore drill | 30–60 min | DR: Re-set custom database GUCs after schema restore — custom `app.*` GUCs required by the app/worker are set on the restored database. | -| 71 | `#199` | A3 | Operator — DR/SRE | After schema restore; Deno v2 available | 1–2 hours | DR: Redeploy Supabase edge functions (Deno v2.x) — required edge functions are deployed to the target project with Deno v2.x. | -| 72 | `#200` | A3 | Operator — DR/SRE | After schema restore drill | 1–2 hours | DR: Re-enter dashboard config after schema restore — auth providers/SSO redirect URLs, connection-pool caps, per-project keys, and `E2E_USER_*` are re-entered in the Supabase/Railway dashboards after restore. | -| 73 | `#183` | A2 | Operator — Sentry + Specialist | Next approved observability window with SENTRY_AUTH_TOKEN | 1–2 hours | Create Sentry metric alert for production DB span p95 > 500ms (`span.op:db`, environment production). **Stop:** no secret printing; blocked until token/env available. | -| 74 | `#206` | A2 | Specialist — answer UI contract | With AnswerState producer work (`#207`) | 2–4 hours | `partial_retrieval` has no app-facing producer — decide RAG contract vs UI-only mapping before AnswerCard. **Stop:** no retrieval behaviour change without RAG flag. | -| 75 | `#208` | A2 | Specialist — clinical copy | With answer clipboard / PR-13 work | 1–2 hours | `answerClipboardText` must not replace `formatAnswerRenderCopyText` — compose render-policy warnings. **Gate:** focused clipboard/copy tests. **Stop:** do not drop render-policy caveats. | -| 76 | `#216` | A2 | High — design-system answer shell | After `#207` and clinical surface decision | 0.5–1 day | Adopt AnswerCard on the answer surface (deferred from PR-J). Own PR, own `verify:ui`. **Stop:** not before `#207`; show both surface treatments before choosing. | -| 77 | `#232` | A2 | High — review ledger hygiene | Next ledger touch for PR-J | 30–60 min | Supersede the PR-J clinical-governance ledger row so it describes the merged head (`ledger:append --supersede`). **Stop:** append-only — never edit/delete the old row. | -| 78 | `#209` | A3 | High — design tokens / contrast | Next Gate 1 / warning-token pass | 1–2 hours | Add contrast pair for `--warning` used as body text (VerificationNotice / DoseLine). **Gate:** design-system contrast checks. **Stop:** do not invent a new status token without TOKENS.md. | -| 79 | `#211` | A3 | High — TypeScript strictness | Dedicated migration branch | multi-PR | Plan and start `noUncheckedIndexedAccess` migration (1266 errors); highest-risk files first. **Stop:** do not flip the flag on main without a staged plan. | -| 80 | `#212` | A3 | High — runtime validation | After highest-risk cast inventory | multi-PR | Replace `as unknown as` and unvalidated `JSON.parse` with Zod/guards at trust boundaries. **Stop:** RAG/provider boundaries need clinical/privacy care. | -| 81 | `#213` | A3 | High — error handling | Next fetch/stream hardening pass | 0.5–1 day | Stop swallowing fetch/stream errors with empty catches; check `response.ok`. **Stop:** do not change telemetry contracts silently. | -| 82 | `#215` | Optional | High — image perf | Next image/PWA pass | 2–4 hours | Image-optimization basics for lightbox, PWA lifecycle, demo PNGs. **Stop:** optional until measured need. | -| 83 | `#221` | A3 | High — design-system convergence | After `#218` cn() decision | 0.5–1 day | Converge remaining local EmptyState/LoadingState/Chip duplicates. **Stop:** not piecemeal before cn()/Chip decisions. | -| 84 | `#222` | A3 | High — headers / search chrome | During headers redesign decision | 2–4 hours | Decide whether mode-home-template / search-results-header-band are in PageHeader scope or permanently out. **Stop:** do not flatten phone composer ownership. | -| 85 | `#233` | A3 | High — design-system docs | Next COMPONENTS.md docs PR | 1–2 hours | Refresh section 0 maturity matrix and document FormField optionality-marker contract. **Gate:** docs checks. **Stop:** docs-only; no product behaviour change. | -| 86 | `#234` | A3 | High — design-system docs | With answer-surface docs | 30–60 min | Document `answer-copy-payload.ts` as the clipboard contract for three surfaces. **Stop:** do not add a second copy builder. | -| 87 | `#235` | A3 | High — design-system evidence | Next warmed local proof-shot pass | 1–2 hours | Capture missing ADOPTION.md §7 proof shots for adopted surfaces. **Stop:** not visual-baseline PNGs (`#118`); no Playwright snapshot commit. | -| 88 | `#236` | Optional | High — branch hygiene | Next cleanup batch with `#079` | 30–60 min | Dispose orphan DS V2 builder branches and leftover wave-5 dev servers. **Stop:** content-verify before delete; no force-clean. | -| 89 | `#237` | A3 | High — design-system a11y | Before freezing Linux visual baselines (#242) | 30–60 min | Eyeball low-confidence AccessibleTable densities at 320px; MissingValue phrases must remain readable. **Gate:** visual spot-check only. **Stop:** do not abbreviate MissingValue to a dash. | -| 90 | `#238` | A3 | High — overlays/UI | After Sheet portal default change (#1616) | 30–60 min | Visual pass for Sheet portal default on settings, sidebar, and answer overlays under OverlayRoot. **Stop:** do not revert portal default without evidence. | -| 91 | `#239` | Optional | High — phone chrome | When phone orientation QA is available | 15–30 min | Manual phone rotation check for ResizeObserver-only phone chrome reserve. **Gate:** `verify:phone-chrome` still owns automated coverage. **Stop:** do not widen reserve heuristics without reproduction. | -| 92 | `#240` | Optional | High — design tokens | Next design-owner review | 15–30 min | Confirm tooltip visual hard-clip asymmetry with design owner (sr-only keeps full text). **Stop:** no product change without that confirmation. | -| 93 | `#241` | A3 | High — therapy catalogue | Standing; with any therapy-home change | 15–30 min | Therapy home summary count/slugs remain build-time; keep `build-therapies-index --check` load-bearing. **Stop:** do not bypass the check. | -| 94 | `#242` | A2 | High — design-system baselines | After human review of Linux baselines | 1–2 hours | Commit approved Linux visual baselines and promote adoption not-committed → committed. **Stop:** never commit baselines from an unreviewed machine run. | -| 95 | `#244` | A3 | High — design tokens / forced-colors | With any ckb-v2 forced-colours edit | 15–30 min | Keep grouped dark selectors in the forced-colours media block so specificity matches dark rules. **Stop:** do not trim to a single `.ckb-v2.ckb-v2` selector. | -| 96 | `#245` | A3 | High — cross-mode links | Next CrossModeLinks / analytics pass | 30–60 min | responsive-compact CrossModeLinks keeps duplicate rails in the DOM; prefer one mount or accept test double-counts. **Stop:** do not break phone-only rail contract. | -| 97 | `#248` | A2 | Operator — Supabase + Specialist | After PR #1614 symptom repair; approved live/history window | 1–2 hours | Investigate why 20260705180000 search-health indexes were missing on live despite applied history; decide if drift checks should catch this class. **Stop:** no hosted mutation without approval. | -| 98 | `#249` | A3 | High — agent process | Next issues-skill / plan touch | 1–2 hours | Extend issues/plan with an agent-safe wins classifier (optional filter; no new skill unless reused thrice). **Stop:** do not outrank A1 operator work. | -| 99 | `#250` | A2 | High — multi-agent execution | After Wave 0 queue repair on main (done in this capture); run remaining Wave 0/#202 process gates next on the engineering track | multi-wave | Execute the fastest-wins multi-wave plan (Waves 0–4 + operator track) with parallel agents and per-PR gates. Waves do not outrank A1 acuity. **Stop:** provider/RAG approvals still required where flagged. | -| 100 | `#251` | Optional | High — agent process | Next handoff/gates doc touch | 15–30 min | Handoff checklist pairs gates skill with verification-router; paste decisive proof line. **Stop:** do not stack broad gates by default. | -| 101 | `#253` | A2 | High — phone results UI | Next open-PR sweep | 15–30 min | Decide #1606's fate: the `MobileResultFilterControl` it rewrites was deleted by #247, so there is nothing left to hand-merge. Verify keyboard parity of the replacement sheet on a real device, then close #1606 as superseded. **Stop:** the decision is a human's; do not close #1606 automatically. | -| 102 | `#254` | A2 | Operator — Codex Cloud | Before #1617 leaves draft | 1–2 hours | Re-run Codex Cloud acceptance at the exact current head or mark head-independent evidence. **Stop:** do not treat stale pins as coverage. | -| 103 | `#256` | A2 | High — mode section nav | Next information-page / mode-nav pass | 2–4 hours | Declared information-page section sets whose target ids nothing renders — verify each set against the rendered DOM per route; render anchors or delete the set. **Stop:** do not audit by grepping for `id=` alone (sectionId props exist). | -| 104 | `#257` | Optional | High — formulation/specifiers flake | Standing until second reproduction | 15–30 min | Single unreproduced ui-formulation flake when run with ui-specifiers — record a second sighting only; do not quarantine until three on the same SHA. **Stop:** do not weaken assertions. | -| 105 | `#286` | A3 | High — in-page nav + frontend | After owner go-ahead for the information-page series | 1–2 days | Convert the six pill-rail information pages onto `InPageNavHeader`, widen Server Component–safe actions, then delete `informationPageSectionDefinitions`. **Gate:** focused DOM/contract tests + `verify:phone-chrome` for touched owners. **Stop:** do not convert DocumentViewer here; do not verify anchors by grepping `id=` alone. | -| 106 | `#287` | A3 | High — in-page nav + clinical owner | After `#286`; medications needs an owner product call | 0.5–1 day design + convert | Decide medications tab model, presentations MobileTabs vs `InPageNavHeader`, and factsheets heading→id scheme; convert or record lasting exceptions. **Stop:** do not port medications mechanically. | -| 107 | `#288` | Optional | High — document chrome | After `#286`/`#287`, or when declaring the series complete | 30–60 min | Confirm DocumentViewer non-adoption (already noted in `docs/search-chrome-behaviour.md`) as the final end state, or schedule a separate convergence PR that leaves pinned `--document-*` CSS names untouched. | -| 108 | `#289` | A2 | High — auth/identity | Next auth module touch | 1–2 hours | Export a named helper (e.g. `authorizationIdentity(headers)`) from the auth module and use it at every property-access call site; consider a lint rule or branded type so `.Authorization` stops type-checking at all. **Stop:** do not change `authorizationHeadersForAccessToken` to emit uppercase — lowercase is the correct Fetch/Headers convention and callers that pass the object wholesale to `fetch` depend on it. | +| 14 | `#117` | A3 | High — frontend/perf + product | After per-field card-vs-search decision | 0.5–1 day once fields decided | Cut `/therapy-compass` mobile LCP by trimming or deferring the 690 KB `therapies-index.json` prose payload. Confirm each long-form field is card-rendered, search-matched, or neither before dropping it. Gate: `check:therapy-data-index`, therapy Playwright journeys, `verify:lighthouse`. **Stop:** do not strip fields without that confirmation. | +| 15 | `#118` | A3 | High — CI/visual/perf gates | After `#147` and `#117` (do not bake current breaches) | 2–4 hours | Commit the CI-uploaded visual baselines under the platform-scoped snapshot path, run `check:lighthouse-budget -- --update` on a known-good build, then flip `enforce` so `visual-baseline` and `lighthouse-budget` block. **Stop:** never commit baselines from a developer machine; never enforce while `#147`/`#117` still breach. | +| 16 | `#033` | A3 | Specialist — prompt/source governance | After `#022` and explicit evaluation approval | 1–2 days plus approved eval | Design unknown-vs-adverse metadata wording and prompt tests. Require no supported-grounding drop and zero citation failures; stop on broad over-caveating or degradation. | +| 17 | `#013`, `#016` | A3 | High — bundling/runtime performance | After `#147`/`#117` or equivalent evidence | 0.5–2 days/route | Optimize only a production route with measured payload/render/motion harm. Require material gain plus focused, `verify:cheap`, and browser evidence; stop on small gain. | +| 18 | `#035` | A3 | Specialist — evidence rules | After a demonstrated missed conflict | 0.5–1 day design; code separate | Define a clinically reviewed conflict class with positive and negative fixtures. Stop if no bounded class can be shown; behavior change requires protected review. | +| 19 | `#027` | Optional | Operator — SRE/provider | When an owned external alert path is wanted | 1–2 hours | Decide vendor/cost/privacy/owner; if accepted, prove one non-PHI outage and recovery alert. Stop when no responder owns it. | +| 20 | `#039` | Optional | High — frontend architecture | During a concrete catalogue-toolbar project | 0.5–1 day inventory; 1–3 days code | Converge only repeated toolbar behavior without flattening search semantics. Stop when there is no bounded implementation target. | +| 21 | `#079` | Optional | High — repository hygiene | In explicitly scheduled batches | 30–60 minutes per batch | Disposition at most ten retained worktrees per pass using owner, PR, review-ledger, ancestry, and patch evidence. Preserve every dirty, active, secret-bearing, post-freeze, or ambiguous worktree and stop rather than broad-cleaning. | +| 22 | `#086` | A3 | High — repository structure + Specialist | On explicit go-ahead for X3; later packages own their gates | 1 PR per work order | Ship remaining maturity backlog (X3 rag.ts; X7 src/lib reorg; X6 coverage floors; X5 ACL consolidation; L1 one-shot archive; M1 host hardening) as verified draft PRs from `docs/maturity-backlog-workorders.md`. L4 ledger rotation shipped in #1418. Start with X3 after go-ahead; stop before RAG edits without the flag or X5 without live-DB approval. | +| 23 | `#098` | A3 | High — test infrastructure | Before `#099` or `#101`; it is their enabler | 2–4 hours | Generalise the answer-route preamble guard into a counting-proxy round-trip budget harness over the existing offline fixtures. Must enforce admission-before-scope, never the reverse. No providers, no DB. Stop if it would require live credentials. | +| 24 | `#102` | A3 | Operator — Supabase + Specialist | Next approved index window, after the ordering question is settled | 1–2 hours plus apply | Author the migration (operator SQL alone never reaches staging/DR/local replay), then apply → mirror `schema.sql` → regenerate drift manifest → register `required_indexes`. **Stop:** the RAG-path index is canary-gated, and ordering `fetchDocumentTitleAliasRows`'s unordered `.limit(12)` does not lift that — an imposed order can select a different twelve, so it is a second canary-gated change, not a way out of the first. The byte-identical claim was retracted. | +| 25 | `#099` | A3 | Specialist — answer path | After `#098` | Half a day per sub-item | Remaining fixed per-request round trips: the 8 `setCachedSearch` deferrals (abort semantics + mutation window), the anonymous subject+global limiter pair (needs a new atomic RPC first), and proxy→route identity duplication. Stop before hand-authoring locking SQL. | +| 26 | `#162` | A3 | High — frontend/UI | When starting the mode search redesign package | 0.5–1.5 days | Redesign `/tools?q=` as Compact Results Instrument (direction A): query-as-H1, one composer, dense tool rows, demote cross-mode cards, remove success-green filter banner and home hero on results. Comps in `public/mockups/mode-page-redesign-2026-07/tools-search/`. Verify phone+desktop chrome ownership and `verify:phone-chrome` / focused UI. Stop if scope expands into Tools home redesign without an explicit ask. | +| 27 | `#163` | A3 | High — frontend/UI | After or with `#162` | 0.5–1.5 days | Redesign `/services?q=` as Progressive Referral Workflow (direction B): H1 = query (not match count), progressive shortlist/compare (no always-on decision panel or giant step rail). Comps in `public/mockups/mode-page-redesign-2026-07/services-search/`. Verify referral shortlist still works; stop before changing Services home ModeHome. | +| 28 | `#164` | A3 | High — frontend/UI | Product confirmed Favourites is hybrid dashboard+search (no ModeHome) | 1–2 days | Redesign Favourites as one dashboard + search page: recommended Search-Led Workspace (direction B) — persistent search, sets as chips, Continue + recent + table on empty query, in-place filter on typed query. Comps in `public/mockups/mode-page-redesign-2026-07/favourites-hybrid/`. Do not reintroduce ModeHome for Favourites. Verify desktop+phone; stop before splitting into separate ModeHome routes. | +| 29 | `#202` | A3 | High — agent process | Next issues-skill touch | 30–60 min | Require recommendation/`/issues` answers to revalidate against `origin/main`'s ledger (or state checkout lag). Prevents re-proposing closed work from stale worktrees. | +| 30 | `#186` | A3 | Specialist — RAG ledger accuracy | Before any `#101` canary | 30–60 min | Rewrite open `#101` to credit PR 1474 hydration parallelisation and list only remaining canary-gated candidates. | +| 31 | `#187` | Optional | High — ledger hygiene | When writing the durable notes | 30–60 min | After one-line notes land for `#151`/`#154`, archive those process-lesson rows so the open table stays actionable. | +| 32 | `#090` | A3 | High — eslint toolchain | When ESLint 10 plugin peers are compatible | blocked; revisit monthly | Upgrade the eslint ecosystem to clear remaining dev-scoped high advisories — full `npm audit` reports zero high advisories from the eslint toolchain. | +| 33 | `#100` | A3 | Specialist — answer streaming | After offline Phase 0/1 design proof | provider-gated rollout | Buffered answer generation has no incremental verified delivery — [`verified-answer-incremental-delivery-design.md`](verified-answer-incremental-delivery-design.md) records the clinical-governance decision and staged co… | +| 34 | `#150` | Optional | Operator — review tooling | Next CodeRabbit billing/policy decision | 30–60 min decision | CodeRabbit reviewed none of a full day's PRs; spending cap reached — the repo's second automated reviewer is either funded or acknowledged as absent, rather than appearing to review while skipping. | +| 35 | `#152` | A2 | High — worktree hygiene | Next cleanup batch with #079 | 1–2 hours | Uncommitted work sits in worktrees whose branches are already merged — work that exists in no branch and no PR is either committed or knowingly discarded, not lost to a disk reclaim. | +| 36 | `#155` | A2 | High — agent process | Standing rule; next multi-agent session | process change | Several agent sessions edit the same branch and ledger concurrently — concurrent sessions stop silently undoing each other on shared `claude/*` branches and on this file. | +| 37 | `#165` | A2 | High — clinical UI | Next answer-home UX pass | 0.5–1 day | Adopt a consolidated answer-home notice block — the studies exist, nothing adopts them — the answer hero states its safety obligation, its scope, and its verification requirement as one block in one voice. | +| 38 | `#168` | A3 | High — ledger architecture | With #156 / id-scheme redesign | design first | Sequential issue ids force every concurrent append to conflict — two sessions can append to this ledger at the same time without conflicting. | +| 39 | `#169` | A3 | High — git hygiene | Next branch cleanup batch | 1–2 hours | Local branches carry work that exists on no remote — committed work is not lost when a machine or worktree is reclaimed. | +| 40 | `#170` | A2 | High — phone UI | Next documents/filter phone pass | 0.5–1 day | Documents and therapy already have page-owned phone filter sheets; remaining modes still use inline controls — shared-band Filter+Sheet adoption without regressing those two or sheetless Sort. | +| 41 | `#175` | A2 | Operator — clinical data + Standard | Next therapy catalogue curation window | 2–4 hours | Therapy modality is now null on all 205 records and needs curation or removal — the Therapy detail and recommend screens either show a curated modality or stop carrying the field at all. | +| 42 | `#178` | A3 | High — PR policy | Next pr-policy change | 1–2 hours | pr-policy does not flag operational risk bundled with clinical or UI risk — a PR that mixes operational-risk paths with clinical or UI risk is called out before it merges, because squash-merging that mix destroys per-it… | +| 43 | `#189` | A2 | Specialist — search/RAG budgets | After #098 route residual; before collapsing RPCs | 2–4 hours + canary if behaviour | Pin /api/search route-level round trips and disposition the x3 text RPC probes — a counting-proxy budget drives `POST` `/api/search` (auth/ratelimit/scope/enrichment/telemetry), and the retrieval-core finding that `matc… | +| 44 | `#036` | Optional | Specialist — privacy/schema | When visibility model is redesigned | design + migration | No explicit `is_public` visibility flag on documents — Public-corpus visibility is implicit: `owner_id IS NULL` on an `indexed` document (`resolveSearchScope`). The `metadata.public_corpus` marker is written by the prom… | +| 45 | `#101` | A3 | Specialist — RAG/retrieval | After #186 update + canary approval | canary-gated | Canary-gated retrieval parallelisation candidates — independent retrieval stages stop running serially, proven by a live canary pair. Candidates: metadata/memory/visual hydration triples repeated on four branches (`rag.… | +| 46 | `#142` | Optional | High — docs hygiene | Next docs filing pass | 1–2 hours | Four loose dated docs need source and migration edits before they can be filed — every dated point-in-time doc lives in `docs/audit/` or `docs/archive/` as `docs/README.md` requires, not loose at the `docs/` top level. | +| 47 | `#151` | Optional | Operator — GitHub PAT | When writing the durable note (#187) | 15–30 min | `gh pr checks` cannot read CI, but the Actions API can — nobody concludes CI is unverifiable when it is merely reached through a different endpoint. | +| 48 | `#154` | Optional | High — agent process | When writing the durable note (#187) | 15–30 min | Row ids are not stable identifiers for "did my change land" — an agent confirms work reached `main` by content, never by id, title or PR state. | +| 49 | `#156` | A3 | High — ledger architecture | With #168 id-scheme work | design first | Outstanding-issues ids are still allocated read-modify-write, and Update-branch corrupts the merge — two branches cannot silently claim the same outstanding-issues id, and no merge path can commit a file where they have. | +| 50 | `#177` | A3 | High — therapy catalogue build | Next therapy-index build change | 1–2 hours | Therapy catalogue aliases duplicate 2.53 MB of bytes instead of pointing at the hashed file — the unversioned catalogue aliases stop costing a second copy of every payload in the repo and the image. | +| 51 | `#180` | A3 | High — therapy catalogue build | With #177/#179 | 1–2 hours | build-therapies-index now overwrites its own source input — the therapy catalogue generator has a source it does not also destroy. | +| 52 | `#188` | A3 | Operator — DR/SRE | After any schema restore drill, or next DR review | checklist-owned | Document and track disaster-recovery re-creation checklist as ledger work — the five DR items that do not survive a schema restore are tracked with owners and verify steps, not only in `docs/operator-backlog.md`. | +| 53 | `#190` | A3 | Specialist — RAG structure | On explicit X3 go-ahead | 1 PR per extraction unit | X3: Finish rag.ts monolith decomposition — `src/lib/rag/rag.ts` is decomposed into focused modules per `docs/maturity-backlog-workorders.md` X3, with existing offline RAG contracts green. | +| 54 | `#191` | A3 | Operator — DB + Specialist | Approved live-DB window only | provider-gated | X5: ACL-migration consolidation (provider-gated) — ACL-related migrations are consolidated per maturity work-order X5 without weakening owner-scope/RLS. | +| 55 | `#192` | A3 | High — test coverage | Next coverage-floor pass | 0.5–1 day | X6: Raise clinical/retrieval/answer coverage floors — coverage floors for clinical, retrieval, and answer domains meet the maturity X6 targets with CI enforcing them. | +| 56 | `#193` | A3 | High — src/lib structure | After/with X3 non-protected clusters | 1 PR per cluster | X7: Complete the remaining src/lib domain-directory reorg — remaining `src/lib` clusters sit in their domain directories per X7 follow-on to X2. | +| 57 | `#194` | A3 | High — scripts/docs hygiene | Next scripts archive pass | 1–2 hours | L1: Archive retired backfill one-shots and dead ci-change-scope token — retired `backfill:*` one-shots and the dead `ci-change-scope` token are archived/removed with docs/script index updated. | +| 58 | `#195` | A3 | Operator — GitHub maintainer | Maintainer UI window | 30–60 min | M1: Repo-host hardening (branch protection and required checks) — GitHub branch-protection rulesets and required checks match audit §8 / maturity M1. | +| 59 | `#196` | A3 | Operator — DR/SRE | After schema restore drill | 1–2 hours | DR: Re-create pg_cron schedules after schema restore — ingestion/retention and related pg_cron schedules exist on the target DB after any schema restore. | +| 60 | `#197` | A3 | Operator — DR/SRE | After schema restore drill | 30–60 min | DR: Re-add Vault secrets including cron_ingestion_jwt — required Vault secrets (at least `cron_ingestion_jwt`) are present after schema restore. | +| 61 | `#198` | A3 | Operator — DR/SRE | After schema restore drill | 30–60 min | DR: Re-set custom database GUCs after schema restore — custom `app.*` GUCs required by the app/worker are set on the restored database. | +| 62 | `#199` | A3 | Operator — DR/SRE | After schema restore; Deno v2 available | 1–2 hours | DR: Redeploy Supabase edge functions (Deno v2.x) — required edge functions are deployed to the target project with Deno v2.x. | +| 63 | `#200` | A3 | Operator — DR/SRE | After schema restore drill | 1–2 hours | DR: Re-enter dashboard config after schema restore — auth providers/SSO redirect URLs, connection-pool caps, per-project keys, and `E2E_USER_*` are re-entered in the Supabase/Railway dashboards after restore. | +| 64 | `#183` | A2 | Operator — Sentry + Specialist | Next approved observability window with SENTRY_AUTH_TOKEN | 1–2 hours | Create Sentry metric alert for production DB span p95 > 500ms (`span.op:db`, environment production). **Stop:** no secret printing; blocked until token/env available. | +| 65 | `#206` | A2 | Specialist — answer UI contract | With AnswerState producer work (`#207`) | 2–4 hours | `partial_retrieval` has no app-facing producer — decide RAG contract vs UI-only mapping before AnswerCard. **Stop:** no retrieval behaviour change without RAG flag. | +| 66 | `#232` | A2 | High — review ledger hygiene | Next ledger touch for PR-J | 30–60 min | Supersede the PR-J clinical-governance ledger row so it describes the merged head (`ledger:append --supersede`). **Stop:** append-only — never edit/delete the old row. | +| 67 | `#209` | A3 | High — design tokens / contrast | Next Gate 1 / warning-token pass | 1–2 hours | Add contrast pair for `--warning` used as body text (VerificationNotice / DoseLine). **Gate:** design-system contrast checks. **Stop:** do not invent a new status token without TOKENS.md. | +| 68 | `#211` | A3 | High — TypeScript strictness | Dedicated migration branch | multi-PR | Plan and start `noUncheckedIndexedAccess` migration (1266 errors); highest-risk files first. **Stop:** do not flip the flag on main without a staged plan. | +| 69 | `#212` | A3 | High — runtime validation | After highest-risk cast inventory | multi-PR | Replace `as unknown as` and unvalidated `JSON.parse` with Zod/guards at trust boundaries. **Stop:** RAG/provider boundaries need clinical/privacy care. | +| 70 | `#213` | A3 | High — error handling | Next fetch/stream hardening pass | 0.5–1 day | Stop swallowing fetch/stream errors with empty catches; check `response.ok`. **Stop:** do not change telemetry contracts silently. | +| 71 | `#215` | Optional | High — image perf | Next image/PWA pass | 2–4 hours | Image-optimization basics for lightbox, PWA lifecycle, demo PNGs. **Stop:** optional until measured need. | +| 72 | `#221` | A3 | High — design-system convergence | After `#218` cn() decision | 0.5–1 day | Converge remaining local EmptyState/LoadingState/Chip duplicates. **Stop:** not piecemeal before cn()/Chip decisions. | +| 73 | `#222` | A3 | High — headers / search chrome | During headers redesign decision | 2–4 hours | Decide whether mode-home-template / search-results-header-band are in PageHeader scope or permanently out. **Stop:** do not flatten phone composer ownership. | +| 74 | `#233` | A3 | High — design-system docs | Next COMPONENTS.md docs PR | 1–2 hours | Refresh section 0 maturity matrix and document FormField optionality-marker contract. **Gate:** docs checks. **Stop:** docs-only; no product behaviour change. | +| 75 | `#234` | A3 | High — design-system docs | With answer-surface docs | 30–60 min | Document `answer-copy-payload.ts` as the clipboard contract for three surfaces. **Stop:** do not add a second copy builder. | +| 76 | `#235` | A3 | High — design-system evidence | Next warmed local proof-shot pass | 1–2 hours | Capture missing ADOPTION.md §7 proof shots for adopted surfaces. **Stop:** not visual-baseline PNGs (`#118`); no Playwright snapshot commit. | +| 77 | `#236` | Optional | High — branch hygiene | Next cleanup batch with `#079` | 30–60 min | Dispose orphan DS V2 builder branches and leftover wave-5 dev servers. **Stop:** content-verify before delete; no force-clean. | +| 78 | `#237` | A3 | High — design-system a11y | Before freezing Linux visual baselines (#242) | 30–60 min | Eyeball low-confidence AccessibleTable densities at 320px; MissingValue phrases must remain readable. **Gate:** visual spot-check only. **Stop:** do not abbreviate MissingValue to a dash. | +| 79 | `#238` | A3 | High — overlays/UI | After Sheet portal default change (#1616) | 30–60 min | Visual pass for Sheet portal default on settings, sidebar, and answer overlays under OverlayRoot. **Stop:** do not revert portal default without evidence. | +| 80 | `#239` | Optional | High — phone chrome | When phone orientation QA is available | 15–30 min | Manual phone rotation check for ResizeObserver-only phone chrome reserve. **Gate:** `verify:phone-chrome` still owns automated coverage. **Stop:** do not widen reserve heuristics without reproduction. | +| 81 | `#240` | Optional | High — design tokens | Next design-owner review | 15–30 min | Confirm tooltip visual hard-clip asymmetry with design owner (sr-only keeps full text). **Stop:** no product change without that confirmation. | +| 82 | `#241` | A3 | High — therapy catalogue | Standing; with any therapy-home change | 15–30 min | Therapy home summary count/slugs remain build-time; keep `build-therapies-index --check` load-bearing. **Stop:** do not bypass the check. | +| 83 | `#242` | A2 | High — design-system baselines | After human review of Linux baselines | 1–2 hours | Commit approved Linux visual baselines and promote adoption not-committed → committed. **Stop:** never commit baselines from an unreviewed machine run. | +| 84 | `#244` | A3 | High — design tokens / forced-colors | With any ckb-v2 forced-colours edit | 15–30 min | Keep grouped dark selectors in the forced-colours media block so specificity matches dark rules. **Stop:** do not trim to a single `.ckb-v2.ckb-v2` selector. | +| 85 | `#245` | A3 | High — cross-mode links | Next CrossModeLinks / analytics pass | 30–60 min | responsive-compact CrossModeLinks keeps duplicate rails in the DOM; prefer one mount or accept test double-counts. **Stop:** do not break phone-only rail contract. | +| 86 | `#248` | A2 | Operator — Supabase + Specialist | After PR #1614 symptom repair; approved live/history window | 1–2 hours | Investigate why 20260705180000 search-health indexes were missing on live despite applied history; decide if drift checks should catch this class. **Stop:** no hosted mutation without approval. | +| 87 | `#249` | A3 | High — agent process | Next issues-skill / plan touch | 1–2 hours | Extend issues/plan with an agent-safe wins classifier (optional filter; no new skill unless reused thrice). **Stop:** do not outrank A1 operator work. | +| 88 | `#250` | A2 | High — multi-agent execution | After Wave 0 queue repair on main (done in this capture); run remaining Wave 0/#202 process gates next on the engineering track | multi-wave | Execute the fastest-wins multi-wave plan (Waves 0–4 + operator track) with parallel agents and per-PR gates. Waves do not outrank A1 acuity. **Stop:** provider/RAG approvals still required where flagged. | +| 89 | `#251` | Optional | High — agent process | Next handoff/gates doc touch | 15–30 min | Handoff checklist pairs gates skill with verification-router; paste decisive proof line. **Stop:** do not stack broad gates by default. | +| 90 | `#253` | A2 | High — phone results UI | Next open-PR sweep | 15–30 min | Decide #1606's fate: the `MobileResultFilterControl` it rewrites was deleted by #247, so there is nothing left to hand-merge. Verify keyboard parity of the replacement sheet on a real device, then close #1606 as superseded. **Stop:** the decision is a human's; do not close #1606 automatically. | +| 91 | `#254` | A2 | Operator — Codex Cloud | Before #1617 leaves draft | 1–2 hours | Re-run Codex Cloud acceptance at the exact current head or mark head-independent evidence. **Stop:** do not treat stale pins as coverage. | +| 92 | `#257` | Optional | High — formulation/specifiers flake | Standing until second reproduction | 15–30 min | Single unreproduced ui-formulation flake when run with ui-specifiers — record a second sighting only; do not quarantine until three on the same SHA. **Stop:** do not weaken assertions. | - + ## Open items > **Merged-main canary update (2026-07-23, run `30018289898`):** the new structured report correctly recorded evaluated tree `c24f2e8f2d30d0c59fc1eba025d3dcd63478137e`, run/attempt identity and `cross-region-runner` latency context. Golden retrieval remained 36/36 with document/content recall 1.0 and no failed cases. The 44-case answer gate had grounded-supported and unsupported-correct rates of 1.0, but failed because `neuroleptic-side-effect-escalation` again returned one citation where two are required (citation-failure rate 0.0227). `admission-discharge-comparison` again omitted the specific AKG admission document after `comparison_source_extractive_fallback`; `admission-discharge-coverage-paraphrase` was advisory-only at 24,870 ms. Answer cost was reported as `$0.234736`. Do not retry immediately: retain this as the first structured datapoint, compare it with the scheduled 2026-07-26 report, and keep retrieval/ranking unchanged. @@ -206,124 +190,102 @@ removed after current-main verification; it is not missing recommended work. | #098 | P2 | task | Offline round-trip budget harness for the hot routes | **Outcome:** per-scenario Supabase round-trip counts are pinned by a test, so an extra round trip on a hot path is a red gate rather than an inference. **Done 2026-07-29:** the measurement gap is closed — `Server-Timing` now covers `auth`/`ratelimit`/`scope` on `/api/answer`, `auth`/`ratelimit`/`search`/`total` on `/api/search`, and `auth`/`ratelimit` on `/api/answer/stream` (previously the route the UI actually calls emitted no header at all). Headers flush before the first SSE frame, so in-stream stages cannot reach a header and must NOT be routed through the governed `progress`/`final` contract. `tests/answer-route-preamble.test.ts` pins admission-before-scope (no scope call while the limiter is pending or after a deny) and the client-disconnect abort signal. **Done 2026-07-30 (PR #1450, `1bff4c78`):** the counting proxy exists and the answer path is budgeted. `tests/helpers/supabase-round-trip-counter.ts` counts on **execution, not construction** — a builder that is never awaited costs zero, one awaited twice costs two — which is the distinction that makes the count mean "requests issued". `tests/rag-round-trip-budget.test.ts` pins two offline answer-path scenarios (a single-source source-only answer, and that trips do not scale with the number of retrieved sources) plus three self-tests of the counter, and is registered in `scripts/fixtures/rag-offline-contract-tests.json` so it runs inside the offline contract rather than only on demand. Verified locally, provider-free: `Test Files 1 passed (1)`, `Tests 5 passed (5)`. Its documented blind spot is worth repeating before anyone cites a budget as total cost: it sees only traffic through the wrapped client, so a trip issued via another client instance, a direct `fetch`, or a provider SDK is invisible to it. **Done 2026-07-30 (search *retrieval core*, not the endpoint):** `tests/search-round-trip-budget.test.ts` pins `searchChunksWithTelemetry` — what `/api/search` calls to retrieve — registered in both the contract fixture and `scripts/rag-offline-contract.mjs`. **Corrected after Codex review on PR #1464:** an earlier version of this row and the test itself claimed to pin `/api/search`. They do not. The route's auth, rate limiting, scope resolution, related-document enrichment and telemetry write are all invisible to this suite, so a round trip added to any of them leaves it green — and the refusal budget below is about *retrieval*, not about an adversarial HTTP request, which still pays the route preamble. **The measured shape is itself the finding:** one search costs **11 round trips** — `rag_aliases` 1, `match_document_chunks_text_v2` **3**, `match_document_table_facts_text_v2` **3**, `get_related_document_metadata_v2` 1, `document_index_quality` 1, `document_images` 2 — so the two text RPCs are each issued three times per search. Pinned by total *and* breakdown, because a refactor swapping one probe for an unrelated query would keep the total at 11 while changing the traffic. Deterministic across three consecutive runs. The refusal budget asserts **zero** Supabase traffic, matching `rag.ts`'s claim that prompt-injection intent is refused before any query issues, and was proven against the broken shape: with a non-refused query it fails on the round-trip assertion (`expected 11 to be +0`), which is why that assertion is ordered ahead of the results assertion. **Note 2026-07-30 (corrected):** a work-branch experiment on `origin/work` (`1f52c704`, reverted in `a0cd00ba`) collapsed both text surfaces offline (budget 11→7) but never reached `main` and was never canaried on those SHAs. Live canaries `30579804611` and `30580564419` ran on unrelated `main` docs commits (`fde68ed4` / `4312a214`) and must not be cited as probe-collapse evidence. Next (b) remains open and still needs a real canary pair before any collapse. **Next:** (a) add the route-level budget this suite does not provide — drive `POST` from `src/app/api/search/route.ts` with counted clients, following the `tests/answer-route-preamble.test.ts` pattern, so a round trip added to the route preamble or post-processing is a red gate; (b) do **not** wholesale-collapse the ×3 sibling lexical variants as the next step — the offline collapse on `origin/work` never landed on `main` with a canary on those SHAs (see Note above), so that avenue is not an approved follow-up; any later latency work must use a materially different approach (for example overlap/parallelism that preserves all three variants) or a real canary pair on the changed tip under the usual RAG gate. (c) `scripts/eval-rag-offline.mjs` and `scripts/test-rag-offline.mjs` remain unwired; the offline contract runner is now the de-facto single home for budgets, so either adopt that explicitly here or wire them. | `docs/audit/latency-audit-2026-07-28.md` measurement plan; `src/lib/server-timing.ts`; `src/lib/answer-stream-contract.ts:18-21` | 2026-07-29 | | #099 | P2 | task | Remove the remaining fixed per-request round trips | **Outcome:** the answer path stops paying avoidable per-request Supabase round trips. **Done 2026-07-29:** shared-cache-hit promotion deferred off the response path with its mid-request staleness guard intact and documented (`rag.ts:3234`, `rag-cache.ts`); scope resolution overlapped with the rate-limit RPC, signal threaded so a client disconnect finally cancels its paginated queries (`answer/route.ts`). **REFUTED on PR #1377 review — do not retry:** the same pass also overlapped scope with the rate-limit RPC and aborted it on deny, claiming the limiter could "deny for free". It cannot. With caller-supplied `filters` or explicit ids, scope passes its zero-query early returns (`search-scope.ts:242,253`) into the paginated `documents` loop at `:269`, and an `AbortSignal` cancels the client request without un-executing a statement Postgres already began — so throttled traffic kept burning database capacity while collecting 429s, against `capacity-review.md:106-113`'s first-soft-failure warning. Scope is behind admission again, pinned by `tests/answer-route-preamble.test.ts`. Re-attempting the overlap requires a non-database admission gate ahead of the durable limiter first. **Remaining:** (a) the 8 `setCachedSearch` awaits — deferring changes `throwIfAborted` semantics and widens a real mutation window because the clone happens after an `await`, so each branch needs discharging individually; (b) batch the anonymous subject+global rate-limit pair, which needs a NEW atomic RPC modelled on `consume_summary_rate_limits_atomic` and cannot be called until the operator applies it — `Promise.all` is the WRONG fix because it consumes the global bucket even when the subject bucket already denied; (c) stop the proxy and route handler resolving identity twice per authenticated request — no in-process memo can do this (different `Request` objects), so the proxy must forward unspoofable verified claims via a header it controls. Cross-references #011: halving auth resolutions eases the ~10-connection Auth cap that `capacity-review.md:106-113` calls the first hard failure. | `docs/audit/latency-audit-2026-07-28.md` L1-1/L1-3/L1-4; `src/lib/api-rate-limit.ts:276-282`; `src/proxy.ts:125` | 2026-07-29 | | #100 | P2 | rec | Buffered answer generation has no incremental verified delivery | **Design complete; runtime work remains provider-gated.** [`verified-answer-incremental-delivery-design.md`](verified-answer-incremental-delivery-design.md) records the clinical-governance decision and staged contract: keep the `progress`/`final`/`error` allowlist; disclose bounded, owner-scoped evidence only after the canonical danger-level source-governance refusal permits it, then emit complete answer sections only after each reuses the full production verification boundary; reconcile every preview byte-for-byte with the authoritative `final`; discard all previews on error/cancel/retry; deploy behind separate parse/emission/render flags. Phase 0 contract proof and Phase 1 evidence preview can be developed offline, but visible rollout still needs clinical/browser proof. Phase 2 changes generation architecture and requires explicit approval for answer-quality evals plus a baseline/post live canary pair. **Naive token streaming remains REFUTED:** never re-land `token`, `revising`, provisional prose, or a weaker stream-only verifier. Cross-references #021. | `docs/verified-answer-incremental-delivery-design.md`; `docs/audit/latency-audit-2026-07-28.md` L0-1; `src/lib/answer-stream-contract.ts:18-21` | 2026-07-30 | -| #101 | P3 | rec | Canary-gated retrieval parallelisation candidates | **Outcome:** independent retrieval stages stop running serially, proven by a live canary pair. Candidates: metadata/memory/visual hydration triples repeated on four branches (`rag.ts:2460,2493,2521` and three more) while `rag.ts:2751-2804` already parallelises three RPCs in one `Promise.all`, so the omission is inconsistency rather than intent; the nested `await`-in-loop scope enumeration (`search-scope.ts:202,328`); typeahead results never cached (`rag.ts:2698-2711`); universal-search coalescing (`/api/search` has it, `/api/search/universal` does not). Each changes candidate assembly, truncation, or what the next keystroke returns, so each needs 36/36 retrieval plus recall 1.0 and zero per-case rr regressions. Distinct from #001 (semantic rerank). Resolved #075 and #083 are the precedents for why these are gated rather than free. **Stop:** needs the #098 harness and explicit canary approval first. | `docs/audit/latency-audit-2026-07-28.md` L2-1/L2-2/L2-8/L1-5 | 2026-07-29 | +| #101 | P3 | rec | Canary-gated retrieval parallelisation candidates | IN FLIGHT 2026-08-12 in PR #1835 (ledger doc hygiene + canonical reference cleanup). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** independent retrieval stages stop running serially, proven by a live canary pair. Candidates: metadata/memory/visual hydration triples repeated on four branches (`rag.ts:2460,2493,2521` and three more) while `rag.ts:2751-2804` already parallelises three RPCs in one `Promise.all`, so the omission is inconsistency rather than intent; the nested `await`-in-loop scope enumeration (`search-scope.ts:202,328`); typeahead results never cached (`rag.ts:2698-2711`); universal-search coalescing (`/api/search` has it, `/api/search/universal` does not). Each changes candidate assembly, truncation, or what the next keystroke returns, so each needs 36/36 retrieval plus recall 1.0 and zero per-case rr regressions. Distinct from #001 (semantic rerank). Resolved #075 and #083 are the precedents for why these are gated rather than free. **Stop:** needs the #098 harness and explicit canary approval first. | `docs/audit/latency-audit-2026-07-28.md` L2-1/L2-2/L2-8/L1-5 | 2026-07-29 | | #102 | P3 | task | Apply the additive `documents` index debt (operator) | **Outcome:** bare-column `ILIKE` and the paged status scan on `documents` are index-served on hosted. `documents_title_trgm_idx` indexes a CONCATENATED expression, so the bare-column predicates in `api/documents/route.ts:193` and `rag-candidate-sources.ts:477` (RAG path) cannot use it and fall back to scanning; `search-scope.ts:271-277` sorts per page against the single-column `documents_status_idx`. **Runbook prepared 2026-07-29 — NOT applied, item stays open:** three `CREATE INDEX CONCURRENTLY` statements authored and reviewed in `docs/operator-apply-performance-latency-remediation.md` — additive, though **the "recall is byte-identical" claim was RETRACTED on 2026-07-29 review**: `fetchDocumentTitleAliasRows` (`rag-candidate-sources.ts:482`) applies `.limit(12)` with no `ORDER BY`, so a new index can change which title-alias documents feed candidate assembly. Only the documents-list use stays ordering-safe; `(status,id)` is canary-gated too — see runbook, and making that `.limit(12)` deterministic first does **not** lift the gate — an unordered `LIMIT` has no stable selection to preserve, so imposing an order can pick a different twelve and is itself an ordering behaviour change on a retrieval surface, which AGENTS.md requires a canary pair for. Sequencing the ordering fix first is worthwhile (unordered `LIMIT` on a retrieval input is latent nondeterminism regardless) but yields two canary-gated changes, not one (PR #1377 review). **Deliberately NO migration file:** an additive-index migration without a synchronized `schema.sql` mirror and regenerated drift manifest is exactly what closed PR #1312, and the mirror cannot come first because `required_indexes` in `search_schema_health()` (`schema.sql:3178`) runs against live. **Next (operator):** **author the migration first** — `supabase/migrations/` is the source of truth and `schema.sql` only a mirror, so hand-run operator SQL never reaches staging, disaster-recovery replay, or a local `supabase db reset`, and a `required_indexes` registration would fail there (PR #1377 review); follow the `20260717170000_registry_projection_cleanup.sql` idempotent pattern. **That migration must also carry the health-function change** — `required_indexes` lives inside `search_schema_health()`, which is redefined by `create or replace function` in eleven migrations (copy `20260705180000_reconcile_search_health_indexes.sql:62`); editing `schema.sql:3177` alone moves only the mirror and leaves the indexes unmonitored on hosted (PR #1377 review). Then apply concurrently, confirm `indisvalid`, mirror both the index statements and the identical function body into `schema.sql`, run `npm run drift:manifest` (Docker), and deploy the migration LAST — in that order, in one change. Expect `check:drift` to report them as unexpected between steps 1 and 2. **Rollback is three deployed phases, not the reverse of one:** retract `required_indexes` via its own `create or replace function` migration and deploy → drop concurrently live → only then deploy the `schema.sql` removal plus an idempotent forward `drop index if exists` migration, because Supabase wraps migrations in a transaction and a plain `DROP INDEX` there takes the lock the concurrent procedure exists to avoid (PR #1377 review). | `docs/audit/latency-audit-2026-07-28.md` L2-3/L2-5; `docs/operator-apply-performance-latency-remediation.md` | 2026-07-29 | | #117 | P2 | rec | Therapy Compass catalogue payload is the mobile LCP outlier | **Outcome:** `/therapy-compass` mobile LCP lands near the other mobile routes instead of double them. **Measured 2026-07-30** by the new pre-merge Lighthouse budget: mobile LCP 5229 ms, TBT 612 ms, CLS 0.142, against 2123-2460 ms on every other mobile route and 826 ms on desktop — so it is client-side work under mobile CPU/network throttling, not server latency. **Cause before this PR:** `useTherapyData` fetched `public/therapy-compass-data/therapies-index.json` (the thin browse index; 205 records) for the home/search/pathways screens, so the download plus JSON parse sat on the critical path before content painted. **Current split:** home now fetches `public/therapy-compass-data/therapies-home.211dab554c4ec62d.json` (136,288 bytes raw), pathways use the thin browse index, and search loads the full prose corpus (#1471). 90% of the index weight is long-form clinical prose — indications 159 KB (26%), contraindicationsOrCautions 139 KB (23%), bestUsedFor 73 KB (12%), clinicalSummary 67 KB (11%), patientPopulation 59 KB (10%), targetSymptoms 48 KB (8%) — while name, slug, category, tags and setting together are 54 KB (7%). **Remaining decision for search/pathways: rendered on the card, matched by search, or neither.** `therapy-card.tsx` references five of those prose fields and the same index feeds the search screen, so stripping fields could silently change clinical display or search recall. **Next:** settle that per-field question, then either pre-truncate prose that only feeds card display, or move search matching server-side / load prose on first keystroke. **Gate:** `check:therapy-data-index` plus the therapy Playwright journeys; re-measure with `npm run verify:lighthouse`. **Stop:** do not drop a field from the catalogue payload without confirming no card renders it and no search path matches on it. Same class as #013 (route-chunk / catalogue JSON weight), different route and now measured. | session 2026-07-30 Lighthouse budget first run; PR #1404; PR #1489 | 2026-07-30 | -| #118 | P2 | task | Adopt the visual and Lighthouse baselines so the two new gates actually gate | OWNER DECISION 2026-08-09 — visual-baseline is NOT to be promoted to pr-required, and no longer runs on pull_request or merge_group (PR #1755). Supersedes step (3) of this row for the visual half: do not re-add pull_request or add the job to pr-required without the owner saying so. Reason: a pixel gate that reports on every UI PR taxes the work it exists to support — during a redesign the honest result is red on every push, and baselines cannot be refreshed until a run has produced the new images, so the red is unavoidable rather than actionable. It was never merge-blocking (continue-on-error, absent from pr-required's needs list) but the notification churn on PR #1743 was real friction. Drift is still caught on pushes to main/release, the weekly schedule, and workflow_dispatch, where a refresh costs one command: npm run design-system:baselines:adopt (added PR #1743). Accepted trade-off: a PR can now move a surface and land before anything notices; the red appears on the next main push. Narrower alternative if pre-merge coverage is ever wanted back: run it only on PRs carrying an opt-in label. The Lighthouse half of this row is unaffected and still fails repo-wide for the Chromium 150-vs-151 baseline reason recorded above. | session 2026-07-30; PR #1404 | 2026-07-30 | -| #142 | P3 | task | Four loose dated docs need source and migration edits before they can be filed | **Outcome:** every dated point-in-time doc lives in `docs/audit/` or `docs/archive/` as `docs/README.md` requires, not loose at the `docs/` top level. **Detail:** PR #1436 filed the five that were docs-only moves. These four are referenced from outside `docs/`, so relocating them means editing source, tests and migration SQL comments — a different risk class than a docs tidy, and not worth bundling into one: `capacity-review.md` (`scripts/soak-test.ts`), `tenancy-defense-in-depth-review.md` (`src/lib/owner-scope.ts`, `tests/owner-scope-guard.test.ts`, two migrations, `SECURITY.md`, `.claude/agents/clinical-governance-reviewer.md`), `operator-apply-july8-batch.md` (three migrations plus `supabase/schema.sql`), `scale-readiness-review.md` (one migration). Also note `forward-codify-retrieval-rpcs-workorder.md` is indexed as a completed workorder but live is still ahead of the repo on those RPCs, so archiving it would misrepresent open operator work. **Next:** treat as low priority — the docs are correctly indexed and reachable where they are; only file them if a pass is already editing those migrations. Editing applied migration SQL is subject to `npm run check:migration-role`'s immutability pin. | PR #1436; session 2026-07-30 | 2026-07-30 | -| #147 | P2 | rec | Mobile CLS breaches four routes, and it reproduces exactly offline | **Outcome:** mobile CLS is under 0.1 on every measured route, or each remaining breach has a recorded reason. **Measured 2026-07-30 with the LOCAL OFFLINE production harness** (`CHROME_PATH=/opt/pw-browsers/chromium npm run verify:lighthouse -- --keep`; Lighthouse 12.8.2, Chromium 141.0.7390.37, demo corpus, inert loopback Supabase). Mobile CLS: `/dsm` 0.363, `/documents/search` 0.220, `/therapy-compass` 0.142, `/` 0.023 — `/forms` did not measure locally (Lighthouse `NO_NAVSTART`, its own "run again" transient), but the live dispatch put it at 0.212. Desktop passes everywhere: 0.016–0.097. **The finding that matters is that these reproduce the live production dispatch EXACTLY.** Against run `30548662649` on `psychiatry.tools`, all four locally measurable mobile routes match to three decimals — 0.023 / 0.142 / 0.220 / 0.363 on both — and the desktop range matches at both endpoints, 0.016–0.097. So CLS on these routes is deterministic layout structure, not network, latency, or production data. Three consequences: (a) the CLS half of `#017` is debuggable and gate-able locally at zero provider cost; (b) `#017`'s recorded blocker "prod server hard-requires Supabase secrets" is stale — `scripts/run-lighthouse-budget.mjs` builds and serves an offline production app precisely to sidestep that, and its earlier note that dev-mode CLS looked "excellent at 0.00–0.04" was measuring DEV, which does not reproduce production; (c) re-dispatching the live workflow to re-measure CLS buys nothing. **LCP does NOT reproduce and must not be read from local runs:** local mobile LCP is 2014–2051 ms against 3639–4716 ms live, because the loopback server has no network latency. The LCP _ranking_ does survive — `/therapy-compass` is the outlier both locally (4978 ms, ~2.4x every other route) and live — which corroborates `#117` rather than replacing it. Mobile TBT, local: `/documents/search` 581 ms, `/dsm` 448, `/therapy-compass` 379, `/` 338. **Ranking by measured contribution, which is what `#017` asked for:** 1. `/dsm` CLS 0.363 (3.6x the threshold, the worst single number anywhere); 2. `/documents/search` 0.220; 3. `/forms` 0.212 (live only); 4. `/therapy-compass` 0.142, and separately the sole LCP outlier via `#117`; 5. `/` 0.023, which passes and needs nothing. **ATTRIBUTED 2026-07-30 — one cause dominates, and it is a self-inflicted round trip.** Driving Chromium directly against the same offline production build (Lighthouse mobile emulation: 412x823, DPR 1.75, 4x CPU throttle) with a `PerformanceObserver` on `layout-shift` reading `entry.sources[].node`: on `/dsm`, `/documents/search`, `/forms` and `/therapy-compass` the entire main content region moves **down 128px and straight back up 128px** within 15-60ms. Both moves score, so the round trip is pure cost with zero net movement. It is **100% of `/documents/search`** (0.110 down + 0.110 up = 0.220) and about 75% of `/dsm`. The shifting element is the `max-sm:pt-[var(--phone-overlay-chrome-h)]` wrapper around `
`, i.e. the phone overlay chrome reserve. **Mechanism, pinned by a MutationObserver timeline on the root style attribute rather than inferred:** the property goes CSS seed -> `200px` -> `72px`, and the decisive line is that the 200px is written when the header stack **already measures 72px** — `t=1552ms reserve=200px stack=72`, corrected to `72px` at `t=1612ms`. So `usePhoneOverlayChromeReserve` (`src/components/clinical-dashboard/use-phone-overlay-chrome-reserve.ts`) publishes a **stale** measurement: its `useLayoutEffect` reads `stack.offsetHeight` while the stack is still transiently 200px, and the write lands after the stack has already collapsed to 72px; its `ResizeObserver` then corrects it. The CSS seed at `globals.css:375` (`calc(max(0.5rem, var(--safe-area-top)) + var(--shell-header-h))` = 72px on a zero-inset profile) is **correct for the settled stack** — the seed is not the problem. **This corrects the mechanism recorded on `#130`** (now archived), which framed the defect as the seed under-reserving by `max(0, 0.5rem - inset)`, i.e. 0px on a notched iPhone and 8px elsewhere. Measured, the CLS driver is not an 8px seed shortfall but a 128px transient over-reserve written by the hook. Do not reopen `#130` on this; it is recorded here. **Control that makes the attribution trustworthy:** `/` never writes the property at all (no overlay stack) and is the one clean route at 0.023. **Variance, stated rather than smoothed over:** `/dsm` measured 0.363 (3 shift entries) and 0.219 (2 entries) on two runs — the round trip is the stable core and the third entry is intermittent. This harness has no network throttling, so `/forms` (0.310 local vs 0.212 live) and `/therapy-compass` (0.240 vs 0.142) run high locally; only `/dsm` 0.363, `/documents/search` 0.220 and `/` 0.023 reproduced the live dispatch exactly, and only those three should be treated as production-equivalent. **Next:** stop the hook publishing a measurement it is about to revise — defer the first publish until the stack has settled, or let the `ResizeObserver` be the only writer and trust the seed until it fires. That is a phone-chrome change, so it is governed by `docs/search-chrome-behaviour.md` and needs `npm run verify:phone-chrome` plus a before/after CLS pair from this harness. **Harness trap worth keeping:** attaching a `MutationObserver` to `document.documentElement` inside a Playwright `addInitScript` throws when the document element does not exist yet, which silently takes the CLS observer down with it and reports a uniform `CLS=0.000` across every route — a false clean bill, not a pass. Guard the attach. **Original next step, now done:** attribute the shifts to elements. Lighthouse's `layout-shift-elements` audit returned **zero items on every route** — the `cumulative-layout-shift` audit carries only `debugdata` — so attribution needs a driven Chromium session with a `PerformanceObserver` on `layout-shift`, reading `entry.sources[].node` and `entry.value`, against the same offline production build. Start with `/dsm`. **Gate — `#118` owns it, and this row supplies a constraint it was missing.** `#118` already tracks flipping `lighthouse-budget.json` to `enforce: true` and says not to do it "before `#117` or the baseline pins a known-slow route". These numbers make that concrete: pinning a baseline today would bake CLS 0.363 on `/dsm` in as the accepted value, so the CLS fixes must land first or the gate ratifies the breach. Separately, no baseline could have been committed from this run anyway — the grader correctly refused it as incomplete evidence because `/forms` produced no report, and that refusal was not overridden. **Reproduction trap:** without `CHROME_PATH`, `chrome-launcher` cannot find a browser in this container and every route fails; the harness reads `CHROME_PATH`/`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` (`run-lighthouse-budget.mjs:291`) but nothing sets either, even though `PLAYWRIGHT_BROWSERS_PATH` is set. Related to `#121`. Also seen: a truncated `progress` package in the npx cache broke Lighthouse startup entirely; clearing the npx cache entry fixed it. **Stop:** do not re-dispatch the live Web-Vitals workflow to re-measure CLS — it is reproducible offline for free. Do not commit a budget baseline from a run with a failed route. Do not treat local LCP numbers as production LCP. | `#017` live run `30548662649`; local `verify:lighthouse` 2026-07-30; `lighthouse-budget.json` | 2026-07-30 | -| #149 | P2 | issue | `check:installed-lock-parity` only compares seven top-level packages, so transitive drift passes | **Outcome:** a session cannot run local gates against a `node_modules` that silently disagrees with the lockfile. **Evidence 2026-07-30:** the `SessionStart` hook reported "node_modules matches the lockfile, skipping install" while `brace-expansion` was installed at **1.1.16** and the lockfile pinned **1.1.18** — the CVE-2026-14257 patch. Every local gate run in that window was weaker than it appeared, including several `verify:cheap` runs reported as green during PR #1430/#1444 work. It surfaced only because `tests/installed-lock-parity.test.ts` asserts the patched behaviour directly (`maxLength` honoured); its failure was initially mistaken for a pre-existing repo defect, since it also failed on clean `main` **in the same stale container**. `npm ci --include=dev` fixed it. **CORRECTION 2026-07-30 (PR #1496 review, Codex):** an earlier version of this row said the parity check lives only in `verify:ui` and proposed adding it to `verify:cheap`. **Both were wrong** — `verify:cheap:internal` already runs `check:installed-lock-parity`, so that follow-up was a no-op, and it ran green throughout the stale window. The real gap is its **scope**: `scripts/check-installed-lock-parity.mjs` compares only seven top-level packages (`criticalInstalledPackages` = next, react, react-dom, eslint, playwright, typescript, vitest), so a stale **transitive** dependency is invisible to it. Proved by faking `node_modules/brace-expansion` back to `1.1.16` against the `1.1.18` lockfile pin: `npm run check:installed-lock-parity` **exits 0**. That also explains why the hook and the gate both reported parity while the tree was stale — neither was looking at the package that drifted. **Next:** broaden the check beyond the seven names — validate the full installed tree against the lockfile, or stamp the install with the lockfile hash and compare that — so transitive drift is caught rather than assumed absent. **Stop:** do not treat a green `check:installed-lock-parity` as proof the install is lockfile-current; today it means seven packages match. **Do not archive on the audit-worktree disposition (checked 2026-07-31):** several `comprehensive-audit-*` worktrees carry an uncommitted row archiving this as “Resolved 2026-07-31 — `check:installed-lock-parity` now validates every concrete package location in `package-lock.json`, including nested transitives”. That fix exists nowhere: not on `main`, not in any of the 42 worktrees, and not in any open PR. `main` still reads `criticalInstalledPackages = ["next", "react", "react-dom", "eslint", "playwright", "typescript", "vitest"]`. The sibling `#103` disposition from the same worktrees WAS verified true and landed in PR #1528; this one was not. **Second, worse instance measured 2026-08-08 — the gap is not only transitive version drift, it is structural completeness.** Two `npm install` runs were killed mid-extraction (a concurrent session ran `npm cache clean`; both exited **127 with an empty log**, no npm error). What survived looked complete: every package directory present at its locked version, and `check:installed-lock-parity` **passed**. But `@testing-library/dom/types/index.d.ts` was **0 bytes** and `@types/node` was **six files short**, missing `web-globals/importmeta.d.ts`. The result was **163 phantom `tsc` errors** — 135 x TS2305 'no exported member screen', plus `ImportMeta.dirname`, `Timeout` and `unref` failures — that read exactly like real code breakage, and `@rolldown/binding-win32-x64-msvc` was absent so Vitest died with `ERR_DLOPEN_FAILED`. A version comparison of any width cannot catch this; the installed files were the right version and simply incomplete. Cheap detection that worked: run the gate against pristine `origin/main` content as well — an identical error count proves the tree innocent and the install guilty. Repair that worked: `rm -rf node_modules` then ONE `npm install `, which reifies the whole tree from the lockfile without touching `package.json` or `package-lock.json`. Worth folding into this row's fix: a completeness check alongside the version comparison — but zero-byte tracked `.d.ts` plus each package's `main`/`types` entry point is NOT enough on its own. The measured miss was `@types/node/web-globals/importmeta.d.ts`, a non-entry file; `@types/node/index.d.ts` stayed present and non-empty, so that shallow check would still pass. Require a completeness method that rejects structurally incomplete installs when arbitrary package files are missing (for example a lockfile/package file inventory, an extract-integrity stamp, or an equivalent that fails closed on a deleted non-entry path), and prove it with a fixture that deletes a non-entry file while leaving entry points intact. | `scripts/check-installed-lock-parity.mjs`; `tests/installed-lock-parity.test.ts`; `package.json` | 2026-07-30 | -| #150 | P2 | issue | CodeRabbit reviewed none of a full day's PRs; spending cap reached | **Outcome:** the repo's second automated reviewer is either funded or acknowledged as absent, rather than appearing to review while skipping. **Evidence 2026-07-30:** CodeRabbit posted "Review limit reached … Your organization has reached its usage spending cap" on **every** PR opened that day — #1404, #1430, #1444, #1445, #1479 — reviewing none of them. Each notice renders as an ordinary bot comment, so a skimming reader sees reviewer activity where there was no review. The Codex connector was the sole substantive reviewer across those PRs and found three real defects that had survived local gates and self-review: a proxy-variable inference in #1430, an `unset` vs `unspecified` git-attribute conflation in #1444, and an earlier P1 recursive-delete on an unvalidated `--dir`. **Next:** decide whether to raise the cap, switch to label-based opt-in so the budget lands on PRs that need it, or accept single-reviewer coverage explicitly. **ESCALATED 2026-07-30 — both reviewers are now capped, so this row's premise no longer holds.** The analysis above rests on the Codex connector being the surviving reviewer. On PR #1505 the Codex connector posted "You have reached your Codex usage limits for code reviews" while CodeRabbit posted its own spending-cap notice on the same PR. **That PR therefore received zero automated review**, and so will anything opened while both caps hold. This is not a second issue — it is the same one, with the fallback removed. **Why it is worth more than a status note:** on 2026-07-30 the single Codex finding on PR #1459 was correct and changed the outcome — it showed that a claimed `LoadingPanel` verification had matched `ModeHomePageSkeleton` instead, which caused `#105` to be closed on wrong evidence. Local gates did not catch it and neither did self-review; the review did. A window in which neither reviewer runs is a window in which that class of error lands. **Next:** the three options above now need deciding rather than deferring, because "accept single-reviewer coverage" is no longer one of them. Until then, treat any PR merged during a cap window as locally-gated only. **Stop:** do not read a CodeRabbit *or* Codex comment as a completed review without checking it is not a usage-limit notice — during this window both bots posted comments on every PR while reviewing none of them. | PRs #1404/#1430/#1444/#1445/#1479; `.coderabbit.yaml` | 2026-07-30 | -| #151 | P3 | issue | `gh pr checks` cannot read CI, but the Actions API can | **Outcome:** nobody concludes CI is unverifiable when it is merely reached through a different endpoint. **Detail:** the session `gh` credential is a fine-grained PAT with **Actions: read** but **not Checks: read**. `gh pr checks` fails per context with `Resource not accessible by personal access token` and `GET /repos/:o/:r/commits/:sha/check-runs` returns 403, so the obvious routes look like a hard wall. `GET .../commits/:sha/status` succeeds but returns `total: 0`, because this repo reports through Actions rather than legacy commit statuses — a silent empty result that reads like an absence of checks instead of an absence of permission. **The working route** is `gh api "repos/BigSimmo/Database/actions/runs?head_sha="` with a jq filter over `.workflow_runs[]`, which returned CI, PR Policy, PR mergeability, SAST and Secret Scan all `completed/success` for PR #1490. **Cost of not knowing this:** two PRs were handed off as green-locally-but-unverifiable and armed for auto-merge instead of merged; #1490 was then closed unmerged and its unique content lost (re-landed as #152). Separately, `scripts/guard-push.mjs` logs `auto-merge: gh not available` only when the CLI cannot execute; that is a distinct #132 prerequisite, not a Checks-permission symptom. **Next:** either add Checks: read to the PAT so `gh pr checks` works, or document the Actions query as the supported way to read CI here. **Stop:** do not report CI as unreadable without trying the Actions endpoint. | session 2026-07-30; PRs #1490, #1494 | 2026-07-30 | -| #152 | P2 | issue | Uncommitted work sits in worktrees whose branches are already merged | **Outcome:** work that exists in no branch and no PR is either committed or knowingly discarded, not lost to a disk reclaim. **Inventory 2026-07-30**, all from worktrees whose pre-snapshot branch tips were fully merged into `origin/main`, so the uncommitted changes existed nowhere else: `codex/reconcile-immediate-20260730` — 21 files, +395/-200 across 19 tracked, including `.github/workflows/ci.yml`, `package.json` and `docs/scripts-index.md`; `codex/document-results-mockup-20260730` — 8 files (+13/-3 tracked) plus an untracked `document-search-results/page.tsx` under `src/app/mockups/` (named without a full path here because it does not resolve in this repo); `codex/chat-ledger-triage-d344` — `docs/outstanding-issues.md` +59/-61; `claude/section-spy-browser-coverage` — `tests/ui-smoke.spec.ts` +51. A fifth (`claude/frosty-mayer-2c6167`) self-resolved to clean during the session. **Preserved 2026-07-30, not reviewed:** each was committed on its own branch as an unpushed `wip: preserve uncommitted work before worktree cleanup` snapshot, so the work now survives a worktree reclaim — `codex/reconcile-immediate-20260730` `748ef018f`, `codex/document-results-mockup-20260730` `5dbd9f965`, `codex/chat-ledger-triage-d344` `b7eae51a4`, `claude/section-spy-browser-coverage` `d949859c3`. All four worktrees are now clean. None is pushed and none is reviewed or verified; the snapshots exist to stop silent loss, not to endorse the content. First captured in PR #1490, which was closed unmerged; re-landed on `main` by PR #1508. **Next:** per snapshot, review and either promote it to a real branch/PR or `git reset --hard HEAD~1` to drop it. Do not bulk-delete worktrees without this check — it is why only one of the two "safe" candidates was removed in the 2026-07-30 cleanup. | session 2026-07-30 worktree cleanup; PR #1490 (closed); PR #1508 | 2026-07-30 | -| #154 | P3 | rec | Row ids are not stable identifiers for "did my change land" | **Outcome:** an agent confirms work reached `main` by content, never by id, title or PR state. **Detail:** three separate checks gave false answers during the 2026-07-30 session. (1) Grepping row **titles** reported rows missing that were present — a concurrent session had reworded them. (2) Grepping row **ids** reported rows present that were absent — `main`'s `#149`/`#150` were unrelated rows (installed-lock parity, CodeRabbit budget) that took those ids during a parallel renumber, because ids are allocated by read-modify-write against a marker and two branches can allocate the same number. (3) `gh pr view` reported `state=MERGED` for a PR whose branch head was not an ancestor of `main` — normal for squash merges, and separately a PR showed `MERGED` while its content genuinely had not landed yet at the ref that had been fetched. A fourth variant: `git show :` silently mangles under MSYS, so a grep over its empty output returns 0 and reads as absence. **What works:** resolve the blob (`git ls-tree `) and `git cat-file -p` it, then grep for **distinctive prose** from the change. **Cost:** PR #1490 was believed landed, was closed unmerged, and took the only record of four preservation snapshots with it; it had to be reconstructed as #152. **Next:** consider a short note in `AGENTS.md` next to the existing squash-verification guidance. **Stop:** never report work as landed on the strength of an id match, a title match, or a PR state field. | session 2026-07-30; PRs #1490, #1508 | 2026-07-31 | -| #156 | P3 | issue | Outstanding-issues ids are still allocated read-modify-write, and Update-branch corrupts the merge | **Outcome:** two branches cannot silently claim the same outstanding-issues id, and no merge path can commit a file where they have. **Detail:** Residual of archived `#112` (gate landed; underlying read-modify-write race explicitly left open) plus post-`#133` evidence. `#133` fixed the two causes of *conflict frequency* — `#1444` removed `merge=union` and `#1479` excluded the ledger from Prettier so a maximum-width row stops re-padding the whole table. Neither touches **id allocation**, which is still read-modify-write against the `issues:next-id` marker, so two branches open at the same time still pick the same number. Measured on PR #1451 (2026-07-30): one P3 row was renumbered `#135` -> `#141` -> `#145` -> `#147` -> `#149` across four sync cycles, because `main` had taken each id in turn — every renumber was manual. This capture itself demonstrates the same hazard: first written as `#151` on PR #1506, then `#154` / `#155` after earlier main syncs, then `#156` here because `main` had already taken `#154` and `#155` for unrelated rows. Both renumbers happened *after* `#133` was closed, which is the point: the fixes in `#1444` and `#1479` were real, but they were fixes to conflict frequency, and allocation was never the same problem. The sharper finding is the resolution path: the GitHub **Update branch** button pushed a sync to that PR head (`df3f3aeed`) whose auto-merge produced **two rows numbered `#141` and two `next-id` markers**, leaving the marker at `142` — below `main`'s highest id, so the next allocation would have reused a live number. `git merge` reported success; only `npm run check:outstanding-issues` caught it. That guard runs in `verify:cheap` and `static-pr`, so such a head cannot merge — but the corruption is produced by a one-click path that runs no guard, and the cost lands on whoever notices. A second session on the same branch later dropped an entire appended evidence block while resolving this file, which the guard cannot detect at all: it validates ids and structure, not whether a merge kept both sides' prose. **Next:** cheapest first — document that Update branch must not be used on PRs touching this file (prefer `npm run sync:pr-branches:apply`, which the repo already prefers for other reasons), then consider allocating ids from a source that cannot collide (per-row files, or a date-plus-slug id) so concurrent branches never contend. **Stop:** do not reintroduce a merge driver here — `#133` settled that; this is about allocation and about merges that silently drop rows, not about the driver. Do not reopen archived `#112` — keep the gate outcome there and track the residual race here. | PR #1451 sync cycles; `df3f3aeed`; archived `#112`; session 2026-07-30; PR #1506 | 2026-07-31 | -| #155 | P2 | rec | Several agent sessions edit the same branch and ledger concurrently | **Outcome:** concurrent sessions stop silently undoing each other on shared `claude/*` branches and on this file. **Observed across one task on 2026-07-30/31:** (a) PR #1490 was **closed unmerged by another actor while auto-merge was armed**, and because arming had been treated as "done", the only record of four preservation snapshots went with it and had to be reconstructed as #152. (b) Three branches (`claude/organize-local-worktree-d22bc3`, `claude/root-dir-coverage-gate-v2`, `claude/capture-session-followups`) received pushes from a Cursor Agent and a Codex session mid-task, producing repeated non-fast-forward rejections; one rejection was masked because the push was piped to `tail`, so the reported exit code was `tail`'s and the push looked successful. (c) `scripts/guard-push.mjs` correctly refused a push with `Pushing now races the squash-merge and can orphan this commit`, requiring disarm to push then re-arm. (d) Ledger ids were renumbered underneath in-flight work (#135 to #141 to #144), which is the mechanism behind #154. **New evidence 2026-08-07 (Track A1, PR #1678) — the sharpest form yet:** a concurrent session in the same worktree ran a commit BETWEEN this session's `git add -A` and its `git commit`, sweeping this session's staged index into a foreign commit `e422624f4` "Consolidate tailwind and UI fixes" mixed with unrelated mode-nav work, then created `claude/consolidated-ui-fixes`, pointed the in-use branch `claude/tailwind-merge-cn` at it too, and checked the new branch out — moving the worktree's HEAD off the branch being worked on. `git commit` reported "nothing to commit, working tree clean" and exit 1; only reading `git log -1` revealed the commit was not this session's. That foreign commit was built on the stale base `dfcbe2039`, so relative to `origin/main` it would revert PRs #1674 and #1675. Recovery was non-destructive: a fresh worktree off current `origin/main`, `git checkout e422624f4 -- `, re-verify, commit. **Next:** for a green ledger-only PR prefer merging it immediately over arming auto-merge; confirm a push landed with `git ls-remote` rather than the command's exit code; expect ids and row wording to move between reading and writing; after any `git commit`, verify `git log -1` is YOUR message and `git rev-parse --abbrev-ref HEAD` is still YOUR branch. **Stop:** do not treat auto-merge as completion, do not assume a branch you pushed an hour ago still has your commit at its tip, and never resolve a collision by resetting or re-pointing another session's branch. | session 2026-07-30/31; PRs #1490, #1508, #1511 | 2026-07-31 | -| #159 | P2 | rec | Lists naming test files are duplicated, and the stale copy fails by running nothing | **Outcome:** no gate, plan or config names a set of test files in a second place without being derived from the filesystem or asserted against it. **Detail:** four instances found on 2026-07-30, each failing the same way — not red, but silently running nothing: (1) `mockupPatterns` in `scripts/ci-change-scope.mjs` matched `-mockups.tsx` only, missing eight singular `-mockup.tsx` components and three of the five `@mockup` specs, so the advisory lane never started for them (PR #1453); (2) `scripts/phone-chrome-plan.mjs` grepped four ownership journeys out of a filename they had moved out of, selecting zero tests; (3) nothing under `tests/helpers/**` was in `uiPatterns`, so editing a shared Playwright fixture reported `ui_changed=false` and `Production UI` skipped entirely — `zero-touch.ts` alone backs six required specs, and this one predated the PR that found it; (4) a helper-only change ran four grepped cases in one of that helper's three consumers (Codex, PR #1466). Each is now guarded individually — `assertMockupSpecParity`, `tests/playwright-project-isolation.test.ts`, and the consumer-list parity test in `tests/verify-phone-chrome.test.ts` — but the general rule is enforced nowhere, so a fifth instance is a matter of time. **Next:** decide whether one gate can assert the class (any array or regex naming `tests/` paths is checked against `readdirSync`), or whether per-site guards are the honest ceiling. **Stop:** do not treat "CI would still have caught it" as a defence — in (3) CI did not, and in (2) and (4) the local gate the repo tells you to run reported green. | PR #1453; PR #1466; session 2026-07-30 | 2026-07-30 | -| #162 | P2 | task | Redesign Tools search results state (Compact Results Instrument) | **Outcome:** `/tools?q=` is a committed results page: query-as-H1, one composer, dense tool rows; cross-mode demoted; no home hero / green filter banner / dual H1. **Product pick:** direction A from comps in `public/mockups/mode-page-redesign-2026-07/tools-search/`. **Next:** implement A on production Tools search; verify desktop+phone chrome ownership. **Stop:** do not redesign Tools home in the same PR unless asked. Renumbered after `main` took `#161` for mockup hover-token residue. | session 2026-07-31 mode-page design audit | 2026-07-31 | +| #118 | P2 | task | Adopt the visual and Lighthouse baselines so the two new gates actually gate | BROWSER DRIFT RE-MEASURED 2026-08-12 and it is WIDER than this row records. The row states the Lighthouse half 'still fails repo-wide for the Chromium 150-vs-151 baseline reason'. Two local runs today both refused with: 'Evidence incomplete. browser drift on 10 run(s): the baseline was measured by HeadlessChrome/**151**.0.0.0, this run used HeadlessChrome/**141**.0.0.0 — refresh it with the CI Refresh Lighthouse baseline dispatch (workflow_dispatch, refresh_lighthouse_baseline). Nothing is graded from this run.' So the gap is now 141-vs-151, and the practical consequence is concrete: the Lighthouse budget CANNOT grade anything from this container, which is where the cheap offline measurement lives. The measurements themselves are still valid observations — #147 was closed on two of them — they simply cannot be auto-graded against a baseline captured on a different browser. Next for this half: run the refresh_lighthouse_baseline dispatch so the committed baseline matches a browser the local harness can reproduce, OR record that local Lighthouse grading is permanently unavailable and treat CI as the only grader. The visual-baseline half of this row is unchanged and still governed by the 2026-08-09 owner decision above. | session 2026-07-30; PR #1404 | 2026-07-30 | +| #142 | P3 | task | Four loose dated docs need source and migration edits before they can be filed | IN FLIGHT 2026-08-12 in PR #1835 (ledger doc hygiene + canonical reference cleanup). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** every dated point-in-time doc lives in `docs/audit/` or `docs/archive/` as `docs/README.md` requires, not loose at the `docs/` top level. **Detail:** PR #1436 filed the five that were docs-only moves. These four are referenced from outside `docs/`, so relocating them means editing source, tests and migration SQL comments — a different risk class than a docs tidy, and not worth bundling into one: `capacity-review.md` (`scripts/soak-test.ts`), `tenancy-defense-in-depth-review.md` (`src/lib/owner-scope.ts`, `tests/owner-scope-guard.test.ts`, two migrations, `SECURITY.md`, `.claude/agents/clinical-governance-reviewer.md`), `operator-apply-july8-batch.md` (three migrations plus `supabase/schema.sql`), `scale-readiness-review.md` (one migration). Also note `forward-codify-retrieval-rpcs-workorder.md` is indexed as a completed workorder but live is still ahead of the repo on those RPCs, so archiving it would misrepresent open operator work. **Next:** treat as low priority — the docs are correctly indexed and reachable where they are; only file them if a pass is already editing those migrations. Editing applied migration SQL is subject to `npm run check:migration-role`'s immutability pin. | PR #1436; session 2026-07-30 | 2026-07-30 | +| #150 | P2 | issue | CodeRabbit reviewed none of a full day's PRs; spending cap reached | IN FLIGHT 2026-08-12 in PR #1836 (finalize tooling follow-through notes). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** the repo's second automated reviewer is either funded or acknowledged as absent, rather than appearing to review while skipping. **Evidence 2026-07-30:** CodeRabbit posted "Review limit reached … Your organization has reached its usage spending cap" on **every** PR opened that day — #1404, #1430, #1444, #1445, #1479 — reviewing none of them. Each notice renders as an ordinary bot comment, so a skimming reader sees reviewer activity where there was no review. The Codex connector was the sole substantive reviewer across those PRs and found three real defects that had survived local gates and self-review: a proxy-variable inference in #1430, an `unset` vs `unspecified` git-attribute conflation in #1444, and an earlier P1 recursive-delete on an unvalidated `--dir`. **Next:** decide whether to raise the cap, switch to label-based opt-in so the budget lands on PRs that need it, or accept single-reviewer coverage explicitly. **ESCALATED 2026-07-30 — both reviewers are now capped, so this row's premise no longer holds.** The analysis above rests on the Codex connector being the surviving reviewer. On PR #1505 the Codex connector posted "You have reached your Codex usage limits for code reviews" while CodeRabbit posted its own spending-cap notice on the same PR. **That PR therefore received zero automated review**, and so will anything opened while both caps hold. This is not a second issue — it is the same one, with the fallback removed. **Why it is worth more than a status note:** on 2026-07-30 the single Codex finding on PR #1459 was correct and changed the outcome — it showed that a claimed `LoadingPanel` verification had matched `ModeHomePageSkeleton` instead, which caused `#105` to be closed on wrong evidence. Local gates did not catch it and neither did self-review; the review did. A window in which neither reviewer runs is a window in which that class of error lands. **Next:** the three options above now need deciding rather than deferring, because "accept single-reviewer coverage" is no longer one of them. Until then, treat any PR merged during a cap window as locally-gated only. **Stop:** do not read a CodeRabbit *or* Codex comment as a completed review without checking it is not a usage-limit notice — during this window both bots posted comments on every PR while reviewing none of them. | PRs #1404/#1430/#1444/#1445/#1479; `.coderabbit.yaml` | 2026-07-30 | +| #151 | P3 | issue | `gh pr checks` cannot read CI, but the Actions API can | IN FLIGHT 2026-08-12 in PR #1836 (finalize tooling follow-through notes). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** nobody concludes CI is unverifiable when it is merely reached through a different endpoint. **Detail:** the session `gh` credential is a fine-grained PAT with **Actions: read** but **not Checks: read**. `gh pr checks` fails per context with `Resource not accessible by personal access token` and `GET /repos/:o/:r/commits/:sha/check-runs` returns 403, so the obvious routes look like a hard wall. `GET .../commits/:sha/status` succeeds but returns `total: 0`, because this repo reports through Actions rather than legacy commit statuses — a silent empty result that reads like an absence of checks instead of an absence of permission. **The working route** is `gh api "repos/BigSimmo/Database/actions/runs?head_sha="` with a jq filter over `.workflow_runs[]`, which returned CI, PR Policy, PR mergeability, SAST and Secret Scan all `completed/success` for PR #1490. **Cost of not knowing this:** two PRs were handed off as green-locally-but-unverifiable and armed for auto-merge instead of merged; #1490 was then closed unmerged and its unique content lost (re-landed as #152). Separately, `scripts/guard-push.mjs` logs `auto-merge: gh not available` only when the CLI cannot execute; that is a distinct #132 prerequisite, not a Checks-permission symptom. **Next:** either add Checks: read to the PAT so `gh pr checks` works, or document the Actions query as the supported way to read CI here. **Stop:** do not report CI as unreadable without trying the Actions endpoint. | session 2026-07-30; PRs #1490, #1494 | 2026-07-30 | +| #152 | P2 | issue | Uncommitted work sits in worktrees whose branches are already merged | **Outcome:** work that exists in no branch and no PR is either committed or knowingly discarded, not lost to a disk reclaim. **Inventory 2026-07-30**, all from worktrees whose pre-snapshot branch tips were fully merged into `origin/main`, so the uncommitted changes existed nowhere else: `codex/reconcile-immediate-20260730` — 21 files, +395/-200 across 19 tracked, including `.github/workflows/ci.yml`, `package.json` and `docs/scripts-index.md`; `codex/document-results-mockup-20260730` — 8 files (+13/-3 tracked) plus an untracked `document-search-results/page.tsx` under `src/app/mockups/` (named without a full path here because it does not resolve in this repo); `codex/chat-ledger-triage-d344` — `docs/outstanding-issues.md` +59/-61; `claude/section-spy-browser-coverage` — `tests/ui-smoke.spec.ts` +51. A fifth (`claude/frosty-mayer-2c6167`) self-resolved to clean during the session. **Preserved 2026-07-30, not reviewed:** each was committed on its own branch as an unpushed `wip: preserve uncommitted work before worktree cleanup` snapshot, so the work now survives a worktree reclaim — `codex/reconcile-immediate-20260730` `748ef018f`, `codex/document-results-mockup-20260730` `5dbd9f965`, `codex/chat-ledger-triage-d344` `b7eae51a4`, `claude/section-spy-browser-coverage` `d949859c3`. All four worktrees are now clean. None is pushed and none is reviewed or verified; the snapshots exist to stop silent loss, not to endorse the content. First captured in PR #1490, which was closed unmerged; re-landed on `main` by PR #1508. **Next:** per snapshot, review and either promote it to a real branch/PR or `git reset --hard HEAD~1` to drop it. Do not bulk-delete worktrees without this check — it is why only one of the two "safe" candidates was removed in the 2026-07-30 cleanup. NOT VERIFIABLE FROM A CLOUD SESSION (checked 2026-08-12): this row describes machine-local git or process state. Claude Code on the web runs in a container cloned fresh at session start, so it reports 1 worktree, 0 upstream-less branches and no running dev servers no matter what is true on the machine that raised this. Do NOT close this row from a cloud session — that reading is an artifact of the container, not evidence. Verify from the originating machine. | session 2026-07-30 worktree cleanup; PR #1490 (closed); PR #1508 | 2026-07-30 | +| #154 | P3 | rec | Row ids are not stable identifiers for "did my change land" | IN FLIGHT 2026-08-12 in PR #1835 (ledger doc hygiene + canonical reference cleanup). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** an agent confirms work reached `main` by content, never by id, title or PR state. **Detail:** three separate checks gave false answers during the 2026-07-30 session. (1) Grepping row **titles** reported rows missing that were present — a concurrent session had reworded them. (2) Grepping row **ids** reported rows present that were absent — `main`'s `#149`/`#150` were unrelated rows (installed-lock parity, CodeRabbit budget) that took those ids during a parallel renumber, because ids are allocated by read-modify-write against a marker and two branches can allocate the same number. (3) `gh pr view` reported `state=MERGED` for a PR whose branch head was not an ancestor of `main` — normal for squash merges, and separately a PR showed `MERGED` while its content genuinely had not landed yet at the ref that had been fetched. A fourth variant: `git show :` silently mangles under MSYS, so a grep over its empty output returns 0 and reads as absence. **What works:** resolve the blob (`git ls-tree `) and `git cat-file -p` it, then grep for **distinctive prose** from the change. **Cost:** PR #1490 was believed landed, was closed unmerged, and took the only record of four preservation snapshots with it; it had to be reconstructed as #152. **Next:** consider a short note in `AGENTS.md` next to the existing squash-verification guidance. **Stop:** never report work as landed on the strength of an id match, a title match, or a PR state field. | session 2026-07-30; PRs #1490, #1508 | 2026-07-31 | +| #156 | P3 | issue | Outstanding-issues ids are still allocated read-modify-write, and Update-branch corrupts the merge | IN FLIGHT 2026-08-12 in PR #1835 (ledger doc hygiene + canonical reference cleanup). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** two branches cannot silently claim the same outstanding-issues id, and no merge path can commit a file where they have. **Detail:** Residual of archived `#112` (gate landed; underlying read-modify-write race explicitly left open) plus post-`#133` evidence. `#133` fixed the two causes of *conflict frequency* — `#1444` removed `merge=union` and `#1479` excluded the ledger from Prettier so a maximum-width row stops re-padding the whole table. Neither touches **id allocation**, which is still read-modify-write against the `issues:next-id` marker, so two branches open at the same time still pick the same number. Measured on PR #1451 (2026-07-30): one P3 row was renumbered `#135` -> `#141` -> `#145` -> `#147` -> `#149` across four sync cycles, because `main` had taken each id in turn — every renumber was manual. This capture itself demonstrates the same hazard: first written as `#151` on PR #1506, then `#154` / `#155` after earlier main syncs, then `#156` here because `main` had already taken `#154` and `#155` for unrelated rows. Both renumbers happened *after* `#133` was closed, which is the point: the fixes in `#1444` and `#1479` were real, but they were fixes to conflict frequency, and allocation was never the same problem. The sharper finding is the resolution path: the GitHub **Update branch** button pushed a sync to that PR head (`df3f3aeed`) whose auto-merge produced **two rows numbered `#141` and two `next-id` markers**, leaving the marker at `142` — below `main`'s highest id, so the next allocation would have reused a live number. `git merge` reported success; only `npm run check:outstanding-issues` caught it. That guard runs in `verify:cheap` and `static-pr`, so such a head cannot merge — but the corruption is produced by a one-click path that runs no guard, and the cost lands on whoever notices. A second session on the same branch later dropped an entire appended evidence block while resolving this file, which the guard cannot detect at all: it validates ids and structure, not whether a merge kept both sides' prose. **Next:** cheapest first — document that Update branch must not be used on PRs touching this file (prefer `npm run sync:pr-branches:apply`, which the repo already prefers for other reasons), then consider allocating ids from a source that cannot collide (per-row files, or a date-plus-slug id) so concurrent branches never contend. **Stop:** do not reintroduce a merge driver here — `#133` settled that; this is about allocation and about merges that silently drop rows, not about the driver. Do not reopen archived `#112` — keep the gate outcome there and track the residual race here. | PR #1451 sync cycles; `df3f3aeed`; archived `#112`; session 2026-07-30; PR #1506 | 2026-07-31 | +| #155 | P2 | rec | Several agent sessions edit the same branch and ledger concurrently | IN FLIGHT 2026-08-12 in PR #1840 (harden agent handoff, ledger revalidation, wins classifier). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** concurrent sessions stop silently undoing each other on shared `claude/*` branches and on this file. **Observed across one task on 2026-07-30/31:** (a) PR #1490 was **closed unmerged by another actor while auto-merge was armed**, and because arming had been treated as "done", the only record of four preservation snapshots went with it and had to be reconstructed as #152. (b) Three branches (`claude/organize-local-worktree-d22bc3`, `claude/root-dir-coverage-gate-v2`, `claude/capture-session-followups`) received pushes from a Cursor Agent and a Codex session mid-task, producing repeated non-fast-forward rejections; one rejection was masked because the push was piped to `tail`, so the reported exit code was `tail`'s and the push looked successful. (c) `scripts/guard-push.mjs` correctly refused a push with `Pushing now races the squash-merge and can orphan this commit`, requiring disarm to push then re-arm. (d) Ledger ids were renumbered underneath in-flight work (#135 to #141 to #144), which is the mechanism behind #154. **New evidence 2026-08-07 (Track A1, PR #1678) — the sharpest form yet:** a concurrent session in the same worktree ran a commit BETWEEN this session's `git add -A` and its `git commit`, sweeping this session's staged index into a foreign commit `e422624f4` "Consolidate tailwind and UI fixes" mixed with unrelated mode-nav work, then created `claude/consolidated-ui-fixes`, pointed the in-use branch `claude/tailwind-merge-cn` at it too, and checked the new branch out — moving the worktree's HEAD off the branch being worked on. `git commit` reported "nothing to commit, working tree clean" and exit 1; only reading `git log -1` revealed the commit was not this session's. That foreign commit was built on the stale base `dfcbe2039`, so relative to `origin/main` it would revert PRs #1674 and #1675. Recovery was non-destructive: a fresh worktree off current `origin/main`, `git checkout e422624f4 -- `, re-verify, commit. **Next:** for a green ledger-only PR prefer merging it immediately over arming auto-merge; confirm a push landed with `git ls-remote` rather than the command's exit code; expect ids and row wording to move between reading and writing; after any `git commit`, verify `git log -1` is YOUR message and `git rev-parse --abbrev-ref HEAD` is still YOUR branch. **Stop:** do not treat auto-merge as completion, do not assume a branch you pushed an hour ago still has your commit at its tip, and never resolve a collision by resetting or re-pointing another session's branch. | session 2026-07-30/31; PRs #1490, #1508, #1511 | 2026-07-31 | +| #162 | P2 | task | Redesign Tools search results state (Compact Results Instrument) | IN FLIGHT 2026-08-12 in PR #1839 (three runnable directions for the Tools search results state). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** `/tools?q=` is a committed results page: query-as-H1, one composer, dense tool rows; cross-mode demoted; no home hero / green filter banner / dual H1. **Product pick:** direction A from comps in `public/mockups/mode-page-redesign-2026-07/tools-search/`. **Next:** implement A on production Tools search; verify desktop+phone chrome ownership. **Stop:** do not redesign Tools home in the same PR unless asked. Renumbered after `main` took `#161` for mockup hover-token residue. | session 2026-07-31 mode-page design audit | 2026-07-31 | | #163 | P2 | task | Redesign Services search results (Progressive Referral Workflow) | **Outcome:** `/services?q=` uses query-as-H1 (not match-count), progressive shortlist/compare, no always-on decision panel or giant step rail. **Product pick:** direction B from comps in `public/mockups/mode-page-redesign-2026-07/services-search/`. **Next:** implement B; keep referral shortlist behaviour. **Stop:** do not change Services ModeHome in the same PR. | session 2026-07-31 mode-page design audit | 2026-07-31 | | #164 | P2 | task | Redesign Favourites as hybrid dashboard + search (no ModeHome) | **Outcome:** `/favourites` is one dashboard+search workspace; empty query shows Continue/recent/sets/table; typed query filters in place; no ModeHome hero. **Product pick:** Search-Led Workspace (direction B) from comps in `public/mockups/mode-page-redesign-2026-07/favourites-hybrid/`. User rejected ModeHome for Favourites. **Next:** implement B; retire command-library marketing H1 and redundant dual search. **Stop:** do not reintroduce ModeHome or a separate Favourites home route. | session 2026-07-31 mode-page design audit; user Favourites hybrid decision | 2026-07-31 | | #165 | P2 | task | Adopt a consolidated answer-home notice block — the studies exist, nothing adopts them | **Outcome:** the answer hero states its safety obligation, its scope, and its verification requirement as one block in one voice. **Detail:** `/mockups/warning-consolidation` (PR #1437) diagnoses today's three stacked notices — the APP-5 privacy warning at 11px muted, a bare `/privacy` link, and an accent-blue `ShieldCheck` capability claim at 14px semibold — and shows the hierarchy is inverted: the least important line is the loudest, and two shields with opposite meanings sit ~40px apart. Three consolidations are drawn at 1440px and 390px. Recommended: **02 Safety card** on the hero (obligation on a warning-tinted top row, everything descriptive in one grey voice below) and **01 Assurance bar** on the docked composer — the same content model at two densities, so one component with a `density` prop covers both. **This is a governance change, not just a design one:** `PrivacyInputNotice` is the single site-wide APP-5 line and renders on the answer, documents and calculators composers, so all three move together; `tests/privacy-ui.test.ts`, `tests/ui-accessibility.spec.ts` and the phone-chrome reserve coverage all assert against the current markup and must change in the same commit; and the PR will need a full `## Clinical Governance Preflight` (the mockup PR correctly did not). **Third study (before/after):** `/mockups/answer-home-proposal` draws the concrete D-direction proposal as a full hero before/after rather than an isolated notice. **Second study (words only):** `/mockups/warning-line` answers a narrower brief — no icon, border, tint or background, one line where width allows. Six variants A-F; line counts measured from the rendered DOM, not asserted. Only B (middot clauses), D (obligation + verify) and F (compressed obligation) hold one line at desktop width, and **none fit one line on a 390px phone while the pinned APP-5 sentence stays verbatim** — 46 characters of obligation plus the 27-character link exceeds the ~60 available at 11px. Recommended there: **D**, the only compliant variant that is both one line and keeps weight-only hierarchy, reached by dropping the scope claim (a capability statement already visible on the answer itself). F fits best but rewrites the pinned obligation to \|No patient-identifiable information.\| and so needs the same privacy sign-off as `#166` plus a matching `tests/privacy-ui.test.ts` update. **Status:** PR #1437 was closed unmerged on 2026-07-30 as a deliberate pause during an owner-authorized ordered merge sweep, to be reopened at its queued place; branch `claude/warning-consolidation-mockups-09jyj7` is preserved and merged onto current `main`; these follow-up rows have been renumbered on each sync because `main` kept claiming the next ids while the PR was paused; the superseded numbers are deliberately not listed, since they now belong to unrelated rows. **Next:** decide block (02 + 01) versus line (D) direction, get wording sign-off for `#166`, then implement behind one component and run `verify:phone-chrome` before `verify:ui`. | session 2026-07-30; PR #1437; `/mockups/warning-consolidation`; `/mockups/warning-line` | 2026-07-30 | -| #166 | P2 | issue | Answer mode ships no verify-before-use caveat; every other clinical mode does | **Outcome:** the surface that actually generates prose from retrieved sources says so, and says it must be checked. **Detail:** differentials carry "Clinical decision support only. Review before use.", prescribing carries "Confirm against source", specifiers carry a confirm-the-manual line, and calculators carry "Scores support clinical judgement — they never replace a full assessment." The answer hero carries neither an equivalent nor anything about generation: only the APP-5 privacy line and "Searches indexed clinical sources", which reads as assurance rather than caution. `CLAUDE.md` calls this repo a clinical reference prototype and explicitly **not** validated clinical decision support, so the one mode that synthesises text is the one most needing the caveat. Proposed wording, matching the registers above rather than opening a new one: "Answers are AI-generated — verify against the cited source before clinical use." **Independent of `#165`:** even keeping today's three-notice layout, the missing sentence is the gap. **Next:** clinical-governance sign-off on the exact wording, then add it to the answer hero (bundled with `#165` if that lands first). | session 2026-07-30; PR #1437; `src/components/clinical-dashboard/answer-status.tsx` | 2026-07-30 | | #168 | P2 | rec | Sequential issue ids force every concurrent append to conflict | **Outcome:** two sessions can append to this ledger at the same time without conflicting. **Detail:** ids are allocated read-modify-write against the `issues:next-id` marker inside the file being edited, so two branches both read N and both write N. Because duplicate ids are unacceptable, a union merge driver is unsafe — .gitattributes says so explicitly — which is why this file deliberately has no driver and every overlapping append conflicts by hand. Manual resolution is where rows get dropped: PR #1490 was closed during one and took the only record of four snapshots with it (#152), and ids were renumbered under in-flight work three times in one session (#154, #155). The new writer (`scripts/outstanding-issues.mjs`) removes the mechanical errors but explicitly not this one. **Next:** replace the counter with a collision-free id (ULID, timestamp+suffix, or a content hash), keeping a short display number derived at render time if `#151` reads better than 01JQ…; then a union driver becomes safe to reinstate and concurrent appends stop conflicting at all. A larger variant is one row per file under `docs/issues` with the table generated, which the repo already does for `site-map.md`. **Stop:** do not reinstate `merge=union` while ids are sequential — that combination was tried in PR #1416 and removed for duplicating rows and the marker. Renumbered from this PR's original `#159` because `main` already used `#159` for the duplicated test-file-list finding. | session 2026-07-31; .gitattributes; #154/#155; PR #1524 sync | 2026-07-31 | -| #169 | P2 | issue | Local branches carry work that exists on no remote | **Outcome:** committed work is not lost when a machine or worktree is reclaimed. **Detail 2026-07-31:** six `claude/*` branches in this checkout have commits and no `origin/` counterpart. Verified real for `claude/clinical-kb-design-system-333a69` — 57 files / +4069, tip `feat(design-system): v2 token layer, 26 components, browser-crash fix` dated 2026-07-31 17:40, whose added `.design-sync/previews/*.tsx` files are absent from main. Others unverified: `design-sync-db0a54`, `fable-implementation-fc937c`, `frosty-mayer-2c6167`, `issues-133-evidence`. **How to check, because the obvious measure lies:** `git rev-list --count origin/main..` and a three-dot diff both report landed work as unmerged, since this repo squash-merges and the original commits never become ancestors — my own merged branch reported 1 commit and +476 by that measure. Test instead whether files the branch adds exist on main (`git ls-tree origin/main `). **Next:** per branch, push it for review or confirm it is superseded and delete it; do not bulk-delete on the commit count. Sibling of #152, which covers uncommitted work in worktrees rather than unpushed commits on branches. | session 2026-07-31; local branch audit | 2026-07-31 | +| #169 | P2 | issue | Local branches carry work that exists on no remote | **Outcome:** committed work is not lost when a machine or worktree is reclaimed. **Detail 2026-07-31:** six `claude/*` branches in this checkout have commits and no `origin/` counterpart. Verified real for `claude/clinical-kb-design-system-333a69` — 57 files / +4069, tip `feat(design-system): v2 token layer, 26 components, browser-crash fix` dated 2026-07-31 17:40, whose added `.design-sync/previews/*.tsx` files are absent from main. Others unverified: `design-sync-db0a54`, `fable-implementation-fc937c`, `frosty-mayer-2c6167`, `issues-133-evidence`. **How to check, because the obvious measure lies:** `git rev-list --count origin/main..` and a three-dot diff both report landed work as unmerged, since this repo squash-merges and the original commits never become ancestors — my own merged branch reported 1 commit and +476 by that measure. Test instead whether files the branch adds exist on main (`git ls-tree origin/main `). **Next:** per branch, push it for review or confirm it is superseded and delete it; do not bulk-delete on the commit count. Sibling of #152, which covers uncommitted work in worktrees rather than unpushed commits on branches. NOT VERIFIABLE FROM A CLOUD SESSION (checked 2026-08-12): this row describes machine-local git or process state. Claude Code on the web runs in a container cloned fresh at session start, so it reports 1 worktree, 0 upstream-less branches and no running dev servers no matter what is true on the machine that raised this. Do NOT close this row from a cloud session — that reading is an artifact of the container, not evidence. Verify from the originating machine. | session 2026-07-31; local branch audit | 2026-07-31 | | #170 | P2 | task | Phone filter sheets exist for documents and therapy; shared-band adoption remains | **Outcome:** phone filtering opens the repo's own bottom sheet instead of competing for width in the utilities rail. **Current state (2026-08-04):** documents already mounts a Filter documents Sheet, and therapy-compass mounts TherapyFilterSheet; both also pass appliedFilters into SearchResultsHeaderBand. The remaining modes still keep filter controls inline (or page-owned chips/nav), and the shared band has not yet adopted a Filter trigger + Sheet that wraps mobileControls for every mode. **Detail:** SearchResultsHeaderBand still renders mobileControls inline below sm for pages that supply them that way. src/components/ui/sheet.tsx already provides the primitive — bottom sheet on mobile via sheet-up, centred dialog at sm+, safe-area aware, focus-trapped, Escape and backdrop dismiss — so shared-band adoption is the remaining work, not new UI. Design settled in the round-7 study: one Filter control at both widths, badge counting applied filters, sheet titled Filter and sort because a phone bar cannot fit a labelled Sort beside Filter at 390 px. **Next:** replace the inline mobileControls render with a Filter trigger + Sheet containing mobileControls ?? filterControls for the modes that still lack a page-owned sheet, without regressing documents/therapy sheets or removing Sort from sheetless Sort consumers (differentials, forms, services). **Stop:** at least four Playwright specs drive those inline controls directly (document-source-type-select, search-query-ribbon-mobile-control-pair); budget for updating them, and do not ship without verify:ui — PR #1523 showed Production UI critical does gate this surface properly. | Round-7 design study; PR #1523 notes; `src/components/ui/sheet.tsx` | 2026-07-31 | -| #171 | P2 | issue | Documents mode has four overlapping filtering surfaces, two of them the same job | **Outcome:** one Filter control opening one panel, so a reader learns filtering once. **Detail:** `/documents` currently offers (a) smart-tag facets — 11 groups from `smartDocumentFacetGroups`, built by `buildSmartDocumentTagFacetIndex`, the real system; (b) a **source-type** control over `all/tables/images/pdfs`, which duplicates the facet group already named `Document type`; (c) scope chips from `searchCommandSurfaceConfig` (`Guidelines`, `Tables`, `Quotes`, `Current only`) — note `Tables` appears in both (b) and (c); (d) the `Sources` button, which is `openDocumentsDrawer("library")` and is not a filter at all. Traced 2026-07-31; `commandScopes` filter client-side after retrieval in the pages checked (services, prescribing), so merging looks safe from the RAG-protection rules — **verify every mode before editing**, this was not exhaustively checked. **Next:** fold (b) and (c) into the facet panel as groups; take (d) out of the results bar (see `#172`). **Stop:** this is a behaviour change across pages — own PR, own reasoning; do not bundle it with copy or layout work. | `document-search-results.tsx:920-937`; `document-tags.ts:798,849`; session 2026-07-31 | 2026-07-31 | -| #172 | P3 | task | `Sources` sits in the results bar but is navigation, not a filter | **Outcome:** the results bar holds only controls that act on the current results. **Detail:** `Sources` is passed as `utilityControls` from `document-search-results.tsx:1026` and calls `onOpenLibrary` -> `openDocumentsDrawer("library")`. Its own accessible name is *"Open source filters"*, which is why it reads as a second Filter beside the real one. It is also **documents-mode only**, so it can never be a fixture of a shared band. It reaches the ~2,000-document corpus; Filter narrows the ~12 a query returned — zero overlap, which is exactly why they cannot merge. Its one genuinely unique job is telling *"no results"* apart from *"not indexed"*, which matters in a clinical reference. **Next:** move to nav as **Browse library**; consider surfacing *Recently opened* separately since it is used far more often than corpus browsing but currently sits at the same depth. **Stop:** do not delete the capability — only relocate and rename it. | `document-search-results.tsx:1026`; `ClinicalDashboard.tsx:2752`; session 2026-07-31 | 2026-07-31 | -| #174 | P3 | rec | Facets AND within a group, so two values from one group almost always return nothing | **Outcome:** a decision on record, either way. **Detail:** `filterDocumentsBySmartTagFacetIndex` applies `selected.every(...)` across **all** selected keys, with no notion of grouping. So ticking `Medication:Lithium` and `Medication:Clozapine` asks for documents about **both** and returns zero — demonstrated in the round-7 study, where that exact pair is the "No matches" preset. Conventional faceted search ORs within a group and ANDs across groups, which would make that pair mean "either medication". **Next:** decide. If OR-within-group is wanted, it is a small change to the same helper plus its callers, but it changes what every existing multi-select does. **Stop:** this is a product decision, not a defect to fix unilaterally — the current behaviour is self-consistent and may be intended. Flagged, not changed. | `document-tags.ts:849-858`; round-7 study; session 2026-07-31 | 2026-07-31 | -| #175 | P2 | task | Therapy modality is now null on all 205 records and needs curation or removal | **Outcome:** the Therapy detail and recommend screens either show a curated modality or stop carrying the field at all. **Detail:** the source catalogue derived `modality` from each record's own tag list — all 205 records had one, every value was also present in that record's `tags`, and the whole catalogue collapsed to CBT/ACT/DBT. It mislabelled the treatments it could not describe: ECT and rTMS as "ACT", Psychoanalysis and Psychodynamic Psychotherapy as "CBT", MBT and TFP as "DBT". Pre-existing on main, surfaced by the PR #1489 review. The generator emits it only when the source curates a value that is not already a tag, which today means null for 205/205 on the index projections *and* the full catalogue the detail/recommend screens load (`catalogue: "full"`), so the two chips (`detail-screen.tsx:49`, `recommend-screen.tsx:115`) never render and `select.ts:117` contributes no same-modality point. Removal was provably search-neutral: `src/lib/therapies.ts` scores with boolean `haystack.includes(token)`, not term frequency, and every modality value was already contributed by `tags.join(" ")` in the same haystack. **Next:** one of two — curate real modality values in `public/therapy-compass-data/therapies.json` (clinical work, needs the psychiatrist), or drop the field from `types.ts`, `src/lib/therapies.ts`, the two chips and `select.ts`. **Stop:** do not reinstate the tag-derived value to make the chips reappear; a guess rendered as curated fact is the defect. `tests/therapy-compass-pathways.test.ts` pins the echo invariant on both the index and the full catalogue asset. Renumbered from this PR's original `#169` because `main` claimed `#169`–`#174` while the branch was open. | PR #1489 review remediation; PR #1532; session 2026-07-31 | 2026-07-31 | -| #177 | P3 | rec | Therapy catalogue aliases duplicate 2.53 MB of bytes instead of pointing at the hashed file | **Outcome:** the unversioned catalogue aliases stop costing a second copy of every payload in the repo and the image. **Detail:** `public/therapy-compass-data/` ships each catalogue twice — `therapies.json` (2,529,429 bytes) alongside `therapies..json` with identical content, and the same for the index and home projections. Git stores one blob for each identical pair, so history is unaffected, but the working tree and every Docker image carry both. PR #1489's remediation adds a one-deploy grace generation, so a stem whose content changed now holds three copies until the next regeneration. **Next:** serve the alias as a Next rewrite to the current hashed filename instead of writing a duplicate file — the alias would keep its `max-age=0, must-revalidate` header (the hashed target keeps `immutable`, so the rewrite must not inherit it) and the generator would stop copying bytes. Check that a rewrite still satisfies the alias-equality assertions in `check:therapy-data-index` and `tests/therapy-compass-mode-wiring.test.ts`, which currently compare file bytes. **Stop:** do not drop the aliases outright — they are the fallback `useTherapyData` uses when a pre-deploy bundle names a hash that no longer exists. Renumbered from this PR's original `#171`. | PR #1489 review remediation; PR #1532; session 2026-07-31 | 2026-07-31 | -| #178 | P2 | rec | pr-policy does not flag operational risk bundled with clinical or UI risk | **Outcome:** a PR that mixes operational-risk paths with clinical or UI risk is called out before it merges, because squash-merging that mix destroys per-item revert. **Detail:** `classifyPullRequestFiles` already computes `operationalRisk`, `clinicalRisk`, `ragRanking` and `ui` independently, but nothing reacts to the combination. AGENTS.md's "PR bundling" section forbids bundling anything once `operationalRisk` is true; the classifier is where that could be enforced. PR #1489 is the worked example: 33 files spanning `.github/workflows/ci.yml`, both Dockerfiles, a rewrite of the bundle-budget gate, a phone-chrome scroll change and a therapy data restructure, merged as one squash (945148251). Reverting any single item now means hand-reverting hunks out of the squash commit, because the branch commits are unreachable. The remediation PR for that review repeats the pattern on a smaller scale (clinical data plus a one-line ci.yml timeout), which is why this is a recommendation rather than a hard gate — the right severity is probably a warning that names the mixed classes, not a merge block. **Next:** emit an advisory line from `evaluatePullRequestPolicy` when `operationalRisk` coincides with `clinicalRisk` or `ui`, listing which paths drove each; decide separately whether it ever blocks. Cover it in the `--self-test` block. **Stop:** do not make it a hard failure in the same change that introduces it — land the signal first and see how often it fires. Renumbered from this PR's original `#172`. | PR #1489 review remediation; PR #1532; session 2026-07-31 | 2026-07-31 | -| #179 | P3 | issue | The full therapy catalogue silently switched from minified to pretty-printed | **Outcome:** the full catalogue's on-disk format is a decision someone made, not a side effect. **Detail:** `public/therapy-compass-data/therapies.json` was a single minified line (2,529,429 bytes) until PR #1532 routed it through `syncTarget` to scrub the tag-echo `modality`. `syncTarget` serialises with `JSON.stringify(records, null, 2)`, so the file is now 18,400 pretty-printed lines at 2,652,783 bytes. **Measured cost:** +123,354 bytes raw (+4.9%), but only +3,409 bytes gzipped (333,428 -> 336,837, +1.0%) — gzip absorbs almost all the added whitespace, and the file is not JavaScript so `check:bundle-budget` never sees it. The visible cost is git churn: each future data revision now produces ~18,400 changed lines in this file plus another ~18,400 in its content-addressed twin, which is why PR #1532 read as 42,071 insertions when only 381 of them were hand-written. **The benefit is real and probably wins:** a 205-record clinical dataset with a one-line diff is unreviewable, and a reviewer needs to see which clinical field changed. The two index projections were already pretty-printed, so this is now consistent. **Next:** confirm the format deliberately, then either leave it (recommended — 1% wire cost buys reviewable clinical diffs) or pass a minified serialiser for the full payload only, which would land below main's original size now that `modality` is gone. Either way record the reason next to `syncTarget`. **Stop:** do not minify the index or home projections to "match" — they are small and their diffs are the review surface for catalogue changes. | PR #1532; session 2026-07-31 | 2026-07-31 | -| #180 | P3 | issue | build-therapies-index now overwrites its own source input | **Outcome:** the therapy catalogue generator has a source it does not also destroy. **Detail:** `scripts/build-therapies-index.mjs` reads `public/therapy-compass-data/therapies.json` as `source`, and since PR #1532 also writes the curated full payload back to that same path (`syncTarget(legacyFullTarget, curatedFull)`). Input and output are now the same file, so the generator is no longer idempotent with respect to its own input in the strict sense: it is stable in practice only because `curatedModality` is a fixed point (a null modality stays null, a genuinely curated non-tag value survives a second pass). The original tag-echo values are now recoverable only from git history. **Why it matters beyond tidiness:** `#175` asks someone to curate real modality values in this file. They will be editing the generator's output, and any field the generator ever decides to rewrite will silently discard their edit on the next run — with no gate to catch it, because `--check` compares the file against what the generator would produce, so a clobbered hand edit reads as "current". **Next:** separate the two roles — keep an unprocessed source (`therapies.source.json`, or move curation upstream of `public/`) and let `therapies.json` be purely generated, or at minimum add a header comment to the file stating it is generated and hand edits to processed fields will not survive. **Stop:** do not fix this by reverting the full-catalogue scrub; detail and recommend load `catalogue: "full"`, so the scrub is what keeps the ECT/rTMS mislabel off the only screens that render the chip. | PR #1532; session 2026-07-31 | 2026-07-31 | -| #181 | P3 | issue | Correction to `#171`: source-type does NOT duplicate the `Document type` facet group | **CORRECTS a claim already merged to `main` in `#171`.** `#171` states that the documents source-type control "duplicates the facet group already named `Document type`". That is wrong, and it was asserted from a shared word rather than from the code. **What is actually true:** `resultTypeTabs`/`filterMatchesByResultType` (`document-search-results.tsx:253-273`) filter on artefact properties of the file — `match.tableCount > 0`, `match.imageCount > 0`, `match.file_name.endsWith(".pdf")`. The `Document type` **facet** group comes from `label_type: "document_type"` labels, meaning the *kind* of document: policy, guideline, form. A guideline containing a table is both; neither implies the other. They are complementary axes, not duplicates. **The real duplication** is the scope chip `{ id: "tables", label: "Tables" }` in `search-command-surface.ts` against the source-type `Tables` tab — same meaning, two controls. **Consequence for `#171`:** the merge it recommends is still worth doing (four entry points for narrowing one list is too many), but the shape changes — source-type becomes its own group in the panel (Format) rather than being absorbed into `Document type`, and the scope chips are the part that genuinely folds away. `#171`'s other claim, that `Sources` is navigation rather than a filter, is unaffected and stands. **Stop:** do not implement the absorption `#171` describes. **Before merging any of it,** verify the scope-chip semantics per mode — only services and prescribing were traced, and this row exists because an untraced inference reached `main` as fact. | `document-search-results.tsx:253-273`; `document-tags.ts` groupLabels; session 2026-07-31 | 2026-07-31 | +| #175 | P2 | task | Therapy modality is now null on all 205 records and needs curation or removal | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: public/therapy-compass-data/therapies-index.json holds 205 records and 0 carry a modality value, exactly as described. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. **Outcome:** the Therapy detail and recommend screens either show a curated modality or stop carrying the field at all. **Detail:** the source catalogue derived `modality` from each record's own tag list — all 205 records had one, every value was also present in that record's `tags`, and the whole catalogue collapsed to CBT/ACT/DBT. It mislabelled the treatments it could not describe: ECT and rTMS as "ACT", Psychoanalysis and Psychodynamic Psychotherapy as "CBT", MBT and TFP as "DBT". Pre-existing on main, surfaced by the PR #1489 review. The generator emits it only when the source curates a value that is not already a tag, which today means null for 205/205 on the index projections *and* the full catalogue the detail/recommend screens load (`catalogue: "full"`), so the two chips (`detail-screen.tsx:49`, `recommend-screen.tsx:115`) never render and `select.ts:117` contributes no same-modality point. Removal was provably search-neutral: `src/lib/therapies.ts` scores with boolean `haystack.includes(token)`, not term frequency, and every modality value was already contributed by `tags.join(" ")` in the same haystack. **Next:** one of two — curate real modality values in `public/therapy-compass-data/therapies.json` (clinical work, needs the psychiatrist), or drop the field from `types.ts`, `src/lib/therapies.ts`, the two chips and `select.ts`. **Stop:** do not reinstate the tag-derived value to make the chips reappear; a guess rendered as curated fact is the defect. `tests/therapy-compass-pathways.test.ts` pins the echo invariant on both the index and the full catalogue asset. Renumbered from this PR's original `#169` because `main` claimed `#169`–`#174` while the branch was open. | PR #1489 review remediation; PR #1532; session 2026-07-31 | 2026-07-31 | +| #177 | P3 | rec | Therapy catalogue aliases duplicate 2.53 MB of bytes instead of pointing at the hashed file | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: public/therapy-compass-data/ holds THREE copies of the catalogue (therapies.json plus two hashed assets), which is the grace-generation case this row predicted. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. **Outcome:** the unversioned catalogue aliases stop costing a second copy of every payload in the repo and the image. **Detail:** `public/therapy-compass-data/` ships each catalogue twice — `therapies.json` (2,529,429 bytes) alongside `therapies..json` with identical content, and the same for the index and home projections. Git stores one blob for each identical pair, so history is unaffected, but the working tree and every Docker image carry both. PR #1489's remediation adds a one-deploy grace generation, so a stem whose content changed now holds three copies until the next regeneration. **Next:** serve the alias as a Next rewrite to the current hashed filename instead of writing a duplicate file — the alias would keep its `max-age=0, must-revalidate` header (the hashed target keeps `immutable`, so the rewrite must not inherit it) and the generator would stop copying bytes. Check that a rewrite still satisfies the alias-equality assertions in `check:therapy-data-index` and `tests/therapy-compass-mode-wiring.test.ts`, which currently compare file bytes. **Stop:** do not drop the aliases outright — they are the fallback `useTherapyData` uses when a pre-deploy bundle names a hash that no longer exists. Renumbered from this PR's original `#171`. | PR #1489 review remediation; PR #1532; session 2026-07-31 | 2026-07-31 | +| #178 | P2 | rec | pr-policy does not flag operational risk bundled with clinical or UI risk | IN FLIGHT 2026-08-12 in PR #1837 (harden verification & PR policy guards). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** a PR that mixes operational-risk paths with clinical or UI risk is called out before it merges, because squash-merging that mix destroys per-item revert. **Detail:** `classifyPullRequestFiles` already computes `operationalRisk`, `clinicalRisk`, `ragRanking` and `ui` independently, but nothing reacts to the combination. AGENTS.md's "PR bundling" section forbids bundling anything once `operationalRisk` is true; the classifier is where that could be enforced. PR #1489 is the worked example: 33 files spanning `.github/workflows/ci.yml`, both Dockerfiles, a rewrite of the bundle-budget gate, a phone-chrome scroll change and a therapy data restructure, merged as one squash (945148251). Reverting any single item now means hand-reverting hunks out of the squash commit, because the branch commits are unreachable. The remediation PR for that review repeats the pattern on a smaller scale (clinical data plus a one-line ci.yml timeout), which is why this is a recommendation rather than a hard gate — the right severity is probably a warning that names the mixed classes, not a merge block. **Next:** emit an advisory line from `evaluatePullRequestPolicy` when `operationalRisk` coincides with `clinicalRisk` or `ui`, listing which paths drove each; decide separately whether it ever blocks. Cover it in the `--self-test` block. **Stop:** do not make it a hard failure in the same change that introduces it — land the signal first and see how often it fires. Renumbered from this PR's original `#172`. | PR #1489 review remediation; PR #1532; session 2026-07-31 | 2026-07-31 | +| #180 | P3 | issue | build-therapies-index now overwrites its own source input | RE-CONFIRMED STILL LIVE 2026-08-12 on merged main, with the exact lines. scripts/build-therapies-index.mjs takes public/therapy-compass-data/therapies.json as its SOURCE (:24-28, with the missing-source error naming it at :31) and also writes that same path as `legacyFullTarget` (:269). Source and target are one file, which is the defect this row names. Note the sibling that is now closed: the pretty-print regression (#179) is fixed and gated at :240-244, so do not conflate the two — the compactness guard does not address the read-write overlap. Next unchanged: give the generator a source that it never writes (or a target it never reads), so a re-run cannot consume its own output. | PR #1532; session 2026-07-31 | 2026-07-31 | | #183 | P2 | task | Create Sentry metric alert for production DB span p95 > 500ms | Still blocked 2026-08-01 closeout: SUPABASE_ACCESS_TOKEN and SENTRY_AUTH_TOKEN missing from session env; Sentry MCP OAuth can list/get alerts but has no create tool; browser hits login wall; no metric rules exist yet on clinibase-xz. Create Metric Alert: p95(span.duration), filter span.op:db, environment production, threshold >500ms, notify Active Members. Provide SENTRY_AUTH_TOKEN in session to finish via sentry alert metrics create. | session 2026-07-31 db-query-perf follow-up | 2026-07-31 | -| #202 | P3 | rec | Recommendation answers must revalidate the ledger against origin/main, not a stale checkout | **Outcome:** agents stating next recommendations read `origin/main:docs/outstanding-issues.md` (or fast-forward first) and say how far the local checkout lags. **Evidence 2026-07-31:** a Cloud Agent answered `/issues`-style recommendations from a worktree 211 commits behind `origin/main`, so it re-proposed already-closed items (`#113`, `#114`, `#091`, `#105`, …) and missed newer opens (`#147`, `#117`, `#118`). Related to `#154` (ids/titles are unstable) and `#109` (shallow clones), but the failure mode here is stale full history, not missing history. **Next:** add a one-line check to the `issues` skill and AGENTS productivity defaults. **Stop:** do not treat a lagging worktree ledger as current memory. | session 2026-07-31 ledger hygiene review | 2026-07-31 | -| #186 | P3 | task | Update #101 for shipped hydration parallelisation before any further canary work | **Outcome:** `#101` states which parallelisation candidates remain after PR `#1474` (`perf(rag): parallelise independent retrieval hydration`) so the row does not re-propose already-landed work. **Next:** rewrite the open `#101` detail to mark the metadata/memory/visual hydration triples done (with canary evidence citation if recorded) and keep only the still-open candidates (scope enumeration, typeahead cache, universal-search coalescing) with their canary gates. **Stop:** no further retrieval parallelisation without the usual RAG flag + live canary approval. | session 2026-07-31 ledger hygiene review; PR #1474 | 2026-07-31 | -| #187 | P3 | rec | Archive process-lesson rows #151 and #154 once a durable one-line note exists outside the ledger | **Outcome:** `#151` (`gh pr checks` vs Actions API) and `#154` (row ids are not stable land proof) move to Resolved/archive after a durable note lands in `AGENTS.md` or `docs/codex-review-protocol.md`, so the open table is not a graveyard of already-learned lessons. **Next:** add the one-line notes, then `/issues done` each with the doc anchor. **Stop:** do not delete history; archive only. | session 2026-07-31 ledger hygiene review | 2026-07-31 | +| #202 | P3 | rec | Recommendation answers must revalidate the ledger against origin/main, not a stale checkout | IN FLIGHT 2026-08-12 in PR #1840 (harden agent handoff, ledger revalidation, wins classifier). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** agents stating next recommendations read `origin/main:docs/outstanding-issues.md` (or fast-forward first) and say how far the local checkout lags. **Evidence 2026-07-31:** a Cloud Agent answered `/issues`-style recommendations from a worktree 211 commits behind `origin/main`, so it re-proposed already-closed items (`#113`, `#114`, `#091`, `#105`, …) and missed newer opens (`#147`, `#117`, `#118`). Related to `#154` (ids/titles are unstable) and `#109` (shallow clones), but the failure mode here is stale full history, not missing history. **Next:** add a one-line check to the `issues` skill and AGENTS productivity defaults. **Stop:** do not treat a lagging worktree ledger as current memory. | session 2026-07-31 ledger hygiene review | 2026-07-31 | +| #186 | P3 | task | Update #101 for shipped hydration parallelisation before any further canary work | IN FLIGHT 2026-08-12 in PR #1835 (ledger doc hygiene + canonical reference cleanup). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** `#101` states which parallelisation candidates remain after PR `#1474` (`perf(rag): parallelise independent retrieval hydration`) so the row does not re-propose already-landed work. **Next:** rewrite the open `#101` detail to mark the metadata/memory/visual hydration triples done (with canary evidence citation if recorded) and keep only the still-open candidates (scope enumeration, typeahead cache, universal-search coalescing) with their canary gates. **Stop:** no further retrieval parallelisation without the usual RAG flag + live canary approval. | session 2026-07-31 ledger hygiene review; PR #1474 | 2026-07-31 | +| #187 | P3 | rec | Archive process-lesson rows #151 and #154 once a durable one-line note exists outside the ledger | IN FLIGHT 2026-08-12 in PR #1835 (ledger doc hygiene + canonical reference cleanup). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** `#151` (`gh pr checks` vs Actions API) and `#154` (row ids are not stable land proof) move to Resolved/archive after a durable note lands in `AGENTS.md` or `docs/codex-review-protocol.md`, so the open table is not a graveyard of already-learned lessons. **Next:** add the one-line notes, then `/issues done` each with the doc anchor. **Stop:** do not delete history; archive only. | session 2026-07-31 ledger hygiene review | 2026-07-31 | | #188 | P3 | task | Document and track disaster-recovery re-creation checklist as ledger work | **Outcome:** the five DR items that do not survive a schema restore are tracked with owners and verify steps. **Split into concrete tasks 2026-07-31:** `#196` pg_cron; `#197` Vault secrets; `#198` custom GUCs; `#199` edge functions; `#200` dashboard config. Keep this umbrella as the index; execute the child tasks. Distinct from `#056` (staging schema) and resolved `#054` (secrets reconciliation). **Stop:** do not treat a schema restore as complete until children are green. | docs/operator-backlog.md Disaster-recovery re-creation; session 2026-07-31 | 2026-07-31 | | #189 | P2 | task | Pin /api/search route-level round trips and disposition the x3 text RPC probes | **Outcome:** a counting-proxy budget drives `POST` `/api/search` (auth/ratelimit/scope/enrichment/telemetry), and the retrieval-core finding that `match_document_chunks_text_v2` and `match_document_table_facts_text_v2` each issue three times per search is either documented as intentional or collapsed under the RAG canary gate. **Source:** residual next actions on `#098` after answer-path and retrieval-core budgets landed. **Next:** (a) route-level budget following `tests/answer-route-preamble.test.ts`; (b) decide probe vs collapse — behaviour change needs RAG flag + canary. **Stop:** do not change retrieval assembly without approval. | session 2026-07-31; #098 residual; tests/search-round-trip-budget.test.ts | 2026-07-31 | -| #190 | P3 | task | X3: Finish rag.ts monolith decomposition | **Outcome:** `src/lib/rag/rag.ts` is decomposed into focused modules per `docs/maturity-backlog-workorders.md` X3, with existing offline RAG contracts green. **Status:** IN PROGRESS (DocumentViewer/Dashboard extractions done; rag.ts remains). **Next:** continue safe extractions only with the RAG flag before editing protected surfaces; one verified draft PR per unit. **Stop:** no behaviour change without canary when retrieval/answer paths move. | docs/maturity-backlog-workorders.md X3; #086 | 2026-07-31 | +| #190 | P3 | task | X3: Finish rag.ts monolith decomposition | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: src/lib/rag/rag.ts measures 4,362 lines — still the monolith this row describes; the decomposition has not started. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. **Outcome:** `src/lib/rag/rag.ts` is decomposed into focused modules per `docs/maturity-backlog-workorders.md` X3, with existing offline RAG contracts green. **Status:** IN PROGRESS (DocumentViewer/Dashboard extractions done; rag.ts remains). **Next:** continue safe extractions only with the RAG flag before editing protected surfaces; one verified draft PR per unit. **Stop:** no behaviour change without canary when retrieval/answer paths move. | docs/maturity-backlog-workorders.md X3; #086 | 2026-07-31 | | #191 | P3 | task | X5: ACL-migration consolidation (provider-gated) | **Outcome:** ACL-related migrations are consolidated per maturity work-order X5 without weakening owner-scope/RLS. **Next:** DB-owner approved window only; live-DB provider confirmation required before apply. **Stop:** no hosted apply from an agent session without explicit approval. | docs/maturity-backlog-workorders.md X5; #086 | 2026-07-31 | | #192 | P3 | task | X6: Raise clinical/retrieval/answer coverage floors | **Outcome:** coverage floors for clinical, retrieval, and answer domains meet the maturity X6 targets with CI enforcing them. **Next:** set floors from current honest baselines; expand tests only where gaps are real. **Stop:** do not lower floors to pass. | docs/maturity-backlog-workorders.md X6; #086 | 2026-07-31 | -| #193 | P3 | task | X7: Complete the remaining src/lib domain-directory reorg | **Outcome:** remaining `src/lib` clusters sit in their domain directories per X7 follow-on to X2. **Next:** move non-protected clusters first; answer/retrieval clusters need the RAG flag. **Stop:** no drive-by behaviour edits inside moves. | docs/maturity-backlog-workorders.md X7; #086 | 2026-07-31 | -| #194 | P3 | task | L1: Archive retired backfill one-shots and dead ci-change-scope token | **Outcome:** retired `backfill:*` one-shots and the dead `ci-change-scope` token are archived/removed with docs/script index updated. **Status:** IN PROGRESS (#1033 archived m13/july8; backfills still open). **Next:** finish backfill archive + token cleanup in a docs/scripts PR. **Stop:** do not break CI classifiers. | docs/maturity-backlog-workorders.md L1; #086 | 2026-07-31 | +| #193 | P3 | task | X7: Complete the remaining src/lib domain-directory reorg | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: Six domain directories exist under src/lib (extractors, observability, rag, supabase, validation, webhooks); the reorg is genuinely partial, as the row says. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. **Outcome:** remaining `src/lib` clusters sit in their domain directories per X7 follow-on to X2. **Next:** move non-protected clusters first; answer/retrieval clusters need the RAG flag. **Stop:** no drive-by behaviour edits inside moves. | docs/maturity-backlog-workorders.md X7; #086 | 2026-07-31 | +| #194 | P3 | task | L1: Archive retired backfill one-shots and dead ci-change-scope token | VERIFIED CORRECT 2026-08-12 — re-checked against merged main and left open: Still open: five backfill one-shots remain under scripts/ (backfill-document-covers.mjs, backfill-document-tags.ts, backfill-enrichment.ts, backfill-gold-document-labels.ts, backfill-smart-index.ts). No dead ci-change-scope token was found, so that half may already be gone — confirm before archiving the row. **Outcome:** retired `backfill:*` one-shots and the dead `ci-change-scope` token are archived/removed with docs/script index updated. **Status:** IN PROGRESS (#1033 archived m13/july8; backfills still open). **Next:** finish backfill archive + token cleanup in a docs/scripts PR. **Stop:** do not break CI classifiers. | docs/maturity-backlog-workorders.md L1; #086 | 2026-07-31 | | #195 | P3 | task | M1: Repo-host hardening (branch protection and required checks) | **Outcome:** GitHub branch-protection rulesets and required checks match audit §8 / maturity M1. **Next:** maintainer GitHub UI work; not a repo-file change. Record evidence in the ledger when done. **Stop:** agents must not weaken required checks. | docs/maturity-backlog-workorders.md M1; #086 | 2026-07-31 | | #196 | P3 | task | DR: Re-create pg_cron schedules after schema restore | **Outcome:** ingestion/retention and related pg_cron schedules exist on the target DB after any schema restore. **Next:** follow `docs/operator-backlog.md` Disaster-recovery checklist; verify schedules are active. Parent umbrella `#188`. **Stop:** do not skip after a restore. | docs/operator-backlog.md; #188 | 2026-07-31 | | #197 | P3 | task | DR: Re-add Vault secrets including cron_ingestion_jwt | **Outcome:** required Vault secrets (at least `cron_ingestion_jwt`) are present after schema restore. **Next:** operator Vault write + names-only verify. Parent `#188`. **Stop:** never print secret values into git/chat. | docs/operator-backlog.md; #188 | 2026-07-31 | | #198 | P3 | task | DR: Re-set custom database GUCs after schema restore | **Outcome:** custom `app.*` GUCs required by the app/worker are set on the restored database. **Next:** apply from the operator runbook; verify with a read-only show/settings check. Parent `#188`. | docs/operator-backlog.md; #188 | 2026-07-31 | | #199 | P3 | task | DR: Redeploy Supabase edge functions (Deno v2.x) | **Outcome:** required edge functions are deployed to the target project with Deno v2.x. **Next:** operator deploy after restore; confirm function list/health. Parent `#188`. **Stop:** needs Deno toolchain and explicit approval for hosted deploy. | docs/operator-backlog.md; #188 | 2026-07-31 | | #200 | P3 | task | DR: Re-enter dashboard config after schema restore | **Outcome:** auth providers/SSO redirect URLs, connection-pool caps, per-project keys, and `E2E_USER_*` are re-entered in the Supabase/Railway dashboards after restore. **Next:** operator checklist in `docs/operator-backlog.md`. Parent `#188`. **Stop:** do not commit dashboard secrets. | docs/operator-backlog.md; #188 | 2026-07-31 | -| #206 | P2 | task | AnswerState partial_retrieval has no app-facing producer | PR-E step 0 found nothing in the client payload names which expected sources were unavailable (retrievalDiagnostics = candidate counts; conflictsOrGaps = prose). RetrievalStateBanner supports the state but PR-J adoption can only emit ready/stale_evidence/source_only. Next action: decide whether a separate RAG contract PR should add a named missing-source signal (governance preflight + RAG impact line + offline eval); until then do not synthesise the state from counts. Pinned by tests/answer-state-contract.test.ts and SPEC 13 / COMPONENTS 2. | PR-E step 0, session 2026-08-02 | 2026-08-02 | -| #208 | P2 | task | DS V2: answerClipboardText must not replace formatAnswerRenderCopyText in PR 13 | PR 6 strengthened answerClipboardText (unconditional attribution + verify line, enumerated sources, provenance suppressed where it would contradict the caveat). It is still narrower than formatAnswerRenderCopyText (src/lib/answer-render-policy.ts), which carries the render policy's own warnings. PR 13 must compose the two or extend answerClipboardText with clinical-owner review, never swap it in. | clinical-governance-reviewer P1-1 on PR 6; recorded in docs/design-system/SPEC.md and COMPONENTS.md 9.13 | 2026-08-02 | -| #209 | P3 | task | DS V2 Gate 1: add contrast pair for --warning used as body text | VerificationNotice's caution variant and DoseLine's overdue label use --warning at text tier — the only place a status hue is used as body-text colour rather than a --text-* token. Gate 1's contrast checking must add that pair explicitly rather than assuming the text tiers cover it. Also note: the logged-once Sets in missing-value, date-display, verification-notice, answer-state and retrieval-state-banner are module-level, so on the server they are per-process and unbounded; a persistent data defect logs once at boot then is swallowed. Acceptable while unregistered. | clinical-governance-reviewer P3 findings on PR 6; recorded in docs/design-system/SPEC.md PR 6 clinical review note | 2026-08-02 | -| #210 | P2 | task | Restore the npm run typecheck gate | Clear stale .next generated types or update validator references so npm run typecheck reflects source health. Evidence: 14 errors in .next/dev/types/validator.ts referencing removed mockup pages; source-only typecheck is clean. See docs/review-findings-2026-08-02.md sections 2.1 and 9. WIDER THAN TYPECHECK (session 2026-08-06, PR #1647): the trigger is running npm run ensure, which makes the dev server generate .next/dev/types/validator.ts; tsconfig.json includes .next/dev/types/**/*.ts, so that file breaks BOTH repo-wide npm run typecheck AND every Playwright production build, because scripts/run-playwright.mjs writes an isolated tsconfig that extends the root one. Observed as 'Type error: Cannot find name __IsExpected' aborting the isolated build; rm -rf .next/dev restored both. This bites anyone following the documented 'run npm run ensure before browser work' instruction. Next: give the generated Playwright tsconfig its own include rather than inheriting the root include, or stop the dev server writing types into a path the production typecheck reads. | session 2026-08-02 /ledger sweep; docs/review-findings-2026-08-02.md | 2026-08-02 | -| #211 | P2 | task | Plan and start the noUncheckedIndexedAccess migration | Enable noUncheckedIndexedAccess in a branch and remediate the 1,266 errors, starting with the 15-20 highest-risk source files. Hot spots include worker/main.ts:901-942, src/lib/rag/rag-extractive-answer.ts, and src/lib/answer-verification.ts. Prefer ?. or ?? guards, or non-null assertions only where invariants are provable. Re-run npm run test and npm run typecheck before merge. See docs/review-findings-2026-08-02.md section 6. | session 2026-08-02 /ledger sweep — docs/review-findings-2026-08-02.md | 2026-08-02 | -| #212 | P2 | task | Replace as unknown as casts and unvalidated JSON.parse with Zod or runtime guards | 48 as unknown as casts and ~24 unvalidated JSON.parse calls across src/ trust Supabase, OpenAI, localStorage, file metadata and extraction boundaries. Start with src/lib/rag/rag.ts and src/app/api/* routes, mirroring existing Zod use in src/lib/validation/body.ts and src/lib/extractors/document.ts. See docs/review-findings-2026-08-02.md sections 2.2, 2.3 and 8. | session 2026-08-02 /ledger sweep — docs/review-findings-2026-08-02.md | 2026-08-02 | -| #213 | P2 | task | Stop swallowing fetch and stream errors with empty catch handlers | 51 call sites use response.json().catch(() => ({})) or void fetch(...).catch(() => undefined), hiding API, JSON, auth, telemetry and stream failures. Add explicit response.ok checks and a typed safeFetch wrapper that preserves the failure signal for telemetry and user feedback. See docs/review-findings-2026-08-02.md sections 3.1-3.4 and 8. | session 2026-08-02 /ledger sweep — docs/review-findings-2026-08-02.md | 2026-08-02 | -| #215 | P3 | task | Add image-optimization basics for lightbox, PWA lifecycle and demo PNGs | image-lightbox.tsx and pwa-lifecycle.tsx lack decoding=async; public/demo-documents/*.png are ~80 KB each and not served in WebP/AVIF; SignedImage lacks a priority prop for above-fold evidence images. Apply decoding=async, add an optional priority prop, and convert demo PNGs with a PNG fallback. Most performance image findings are already tracked under #016, #013, #117 and #147; this row captures the new image-only observations from the 2026-08-02 audit. | session 2026-08-02 /ledger sweep — docs/audit/performance-image-cwv-audit-2026-08-02.md | 2026-08-02 | -| #216 | P2 | task | Adopt the AnswerCard container on the answer surface (deferred from PR-J) | PR-J adopted the answer safety components (VerificationNotice, RetrievalStateBanner, AnswerState projection, composed clipboard) but NOT AnswerCard itself, so it stays at zero product imports and PR 13's answer surface is adopted in substance, not in shell. The swap replaces the answerSurface wrapper with AnswerCard's article, its query echo with UserQuestionBubble, wraps NaturalLanguageAnswer in its --measure-clamped prose div, maps copy/feedback/follow-up onto its structured actions array, and adds AnswerFooter (needs publisher/version/reviewDate/generatedAt threaded). Deferred deliberately: two competing surface treatments must be resolved by the design owner (SPEC 2.4 border-or-ring), the measure clamp reflows every answer and moves the phone scroll-runway pins that cost PR-V two CI cycles (finding L), and bundling it would make a red verify:ui unattributable across PR-J's other five surfaces. Reasons recorded in docs/design-system/ADOPTION.md 2.6. Next action: own PR after PR-J lands and soaks, own verify:ui pass, own glance; show the user both surface treatments before choosing. | session 2026-08-03 (PR-J Wave 5 controller) | 2026-08-02 | -| #221 | P3 | task | Local EmptyState, LoadingState and Chip duplicates still unconverged after PR-J | PR-J converged what it could inside its allowlists and left four known duplicates, each blocked for a stated reason rather than missed. therapy-compass/ui.tsx defines its own LoadingState AND its own EmptyState used across nine screens (whole-module job, not a one-call-site conversion). mode-home-template.tsx ModeHomeStatusNotice is an EmptyState duplicate that four catalogue homes delegate to, which is why those four files show no diff. differentials-home.tsx has a local two-density Chip blocked by the cn() tailwind-merge gap. favourites-command-library-page.tsx SmallChip is driven by an eight-entry type-token map that Chip's five-tone vocabulary cannot express. Next action: take these as one convergence PR after the cn() decision lands, not piecemeal. Found during PR-J adoption, 2026-08-03. | session 2026-08-03 (PR-J Wave 5, Builder B) | 2026-08-02 | -| #222 | P3 | task | Headers surface only partially converged in PR-J: mode-home-template and search-results-header-band untouched | Builder A converged DsmPageHeader, InformationPageHeader and InformationPageBreadcrumbs onto PageHeader plus Breadcrumb, and declined two files with reasons. mode-home-template.tsx ModeHomeHero is a centred display hero on the fluid text-hero token and is the slot the in-flow phone composer sits in, so converging it onto a left-aligned PageHeader is a redesign of 13 mode homes that collides with the one-composer-per-page contract. search-results-header-band.tsx is a results spine carrying status, counts and filters, not a page-title stack, so its pin tests/search-results-header-band.dom.test.tsx remains unflipped. Both are defensible; both leave the headers surface partially adopted. Next action: decide whether either is in scope at all, or record them as permanently out of the PageHeader vocabulary. Found during PR-J adoption, 2026-08-03. | session 2026-08-03 (PR-J Wave 5, Builder A) | 2026-08-02 | +| #206 | P2 | task | AnswerState partial_retrieval has no app-facing producer | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: `partial_retrieval` is declared in src/lib/answer-state-types.ts:63 and handled in answer-clipboard.ts:75, but nothing in src/app or the retrieval path produces it — still no app-facing producer, as the row says. Do not synthesise it from candidate counts. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. PR-E step 0 found nothing in the client payload names which expected sources were unavailable (retrievalDiagnostics = candidate counts; conflictsOrGaps = prose). RetrievalStateBanner supports the state but PR-J adoption can only emit ready/stale_evidence/source_only. Next action: decide whether a separate RAG contract PR should add a named missing-source signal (governance preflight + RAG impact line + offline eval); until then do not synthesise the state from counts. Pinned by tests/answer-state-contract.test.ts and SPEC 13 / COMPONENTS 2. | PR-E step 0, session 2026-08-02 | 2026-08-02 | +| #209 | P3 | task | DS V2 Gate 1: add contrast pair for --warning used as body text | IN FLIGHT 2026-08-12 in PR #1841 (adds an explicit --warning body-text contrast assertion in tests/design-token-contract.test.ts). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. VerificationNotice's caution variant and DoseLine's overdue label use --warning at text tier — the only place a status hue is used as body-text colour rather than a --text-* token. Gate 1's contrast checking must add that pair explicitly rather than assuming the text tiers cover it. Also note: the logged-once Sets in missing-value, date-display, verification-notice, answer-state and retrieval-state-banner are module-level, so on the server they are per-process and unbounded; a persistent data defect logs once at boot then is swallowed. Acceptable while unregistered. | clinical-governance-reviewer P3 findings on PR 6; recorded in docs/design-system/SPEC.md PR 6 clinical review note | 2026-08-02 | +| #210 | P2 | task | npm run ensure generates .next/dev types that break typecheck and every Playwright build | RETITLED AND RE-SCOPED 2026-08-12 — the original title 'Restore the npm run typecheck gate' is wrong and cost this row its clarity: the gate was never missing. `npm run typecheck` IS in `verify:cheap:internal` (package.json:71) and runs today. The live defect is the generated-types include: tsconfig.json:28 still lists `.next/dev/types/**/*.ts` in `include`, so running `npm run ensure` — which the repo's own docs tell you to do before any browser work — makes the dev server write .next/dev/types/validator.ts, and that file then breaks BOTH repo-wide `npm run typecheck` AND every Playwright production build, because scripts/run-playwright.mjs writes an isolated tsconfig extending the root one ('Type error: Cannot find name __IsExpected'). `rm -rf .next/dev` restores both. Next: stop including dev-generated types in the checked project (drop `.next/dev/types/**/*.ts` from include, or give the Playwright isolated tsconfig its own include list), then confirm typecheck stays clean after `npm run ensure`. Stop: do not 'fix' this by removing typecheck from the gate — the gate is not the problem. | session 2026-08-02 /ledger sweep; docs/review-findings-2026-08-02.md | 2026-08-02 | +| #211 | P2 | task | Plan and start the noUncheckedIndexedAccess migration | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: `noUncheckedIndexedAccess` is absent from tsconfig.json — the migration has not begun. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. Enable noUncheckedIndexedAccess in a branch and remediate the 1,266 errors, starting with the 15-20 highest-risk source files. Hot spots include worker/main.ts:901-942, src/lib/rag/rag-extractive-answer.ts, and src/lib/answer-verification.ts. Prefer ?. or ?? guards, or non-null assertions only where invariants are provable. Re-run npm run test and npm run typecheck before merge. See docs/review-findings-2026-08-02.md section 6. | session 2026-08-02 /ledger sweep — docs/review-findings-2026-08-02.md | 2026-08-02 | +| #212 | P2 | task | Replace as unknown as casts and unvalidated JSON.parse with Zod or runtime guards | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: 40 `as unknown as` casts remain under src/ — the row's population is intact. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. 48 as unknown as casts and ~24 unvalidated JSON.parse calls across src/ trust Supabase, OpenAI, localStorage, file metadata and extraction boundaries. Start with src/lib/rag/rag.ts and src/app/api/* routes, mirroring existing Zod use in src/lib/validation/body.ts and src/lib/extractors/document.ts. See docs/review-findings-2026-08-02.md sections 2.2, 2.3 and 8. | session 2026-08-02 /ledger sweep — docs/review-findings-2026-08-02.md | 2026-08-02 | +| #213 | P2 | task | Stop swallowing fetch and stream errors with empty catch handlers | SCOPE RE-MEASURED 2026-08-12 on merged main: only **3** empty catch handlers remain under src/ (`catch {}` / `catch (e) {}`), down from the audit population this row was opened against. The principle is unchanged and the remaining three still need dispositioning — each should either handle, log through the observability path, or carry a comment saying why swallowing is correct — but this is now a small, closeable job rather than a sweep. Companion rows measured in the same pass for sequencing: #212 has 40 `as unknown as` casts left, #211's `noUncheckedIndexedAccess` is still absent from tsconfig.json. Do the three catches first; it is the cheapest of the three and no longer blocked behind the other two. | session 2026-08-02 /ledger sweep — docs/review-findings-2026-08-02.md | 2026-08-02 | +| #215 | P3 | task | Add image-optimization basics for lightbox, PWA lifecycle and demo PNGs | PARTIALLY DELIVERED, RE-MEASURED 2026-08-12. One of the three items is done: src/components/clinical-dashboard/image-lightbox.tsx now carries `decoding="async"` (note the path — the row's 'image-lightbox.tsx' is under clinical-dashboard/, and a search at the repo root finds nothing, which reads misleadingly as 'already fixed'). Still outstanding: src/components/pwa-lifecycle.tsx has NO `decoding="async"`; public/demo-documents/*.png are still PNG-only with no WebP/AVIF alternative; and SignedImage still has no `priority` prop for above-fold evidence images. Next: those three, in that order — the pwa-lifecycle attribute is a one-line change. Related image/perf work stays tracked under #016, #013, #117 and #147. | session 2026-08-02 /ledger sweep — docs/audit/performance-image-cwv-audit-2026-08-02.md | 2026-08-02 | +| #221 | P3 | task | Local EmptyState, LoadingState and Chip duplicates still unconverged after PR-J | IN FLIGHT 2026-08-12 in PR #1841 and #1842 (cn()/LinkAction contracts and the EmptyState/Chip convergence). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. PR-J converged what it could inside its allowlists and left four known duplicates, each blocked for a stated reason rather than missed. therapy-compass/ui.tsx defines its own LoadingState AND its own EmptyState used across nine screens (whole-module job, not a one-call-site conversion). mode-home-template.tsx ModeHomeStatusNotice is an EmptyState duplicate that four catalogue homes delegate to, which is why those four files show no diff. differentials-home.tsx has a local two-density Chip blocked by the cn() tailwind-merge gap. favourites-command-library-page.tsx SmallChip is driven by an eight-entry type-token map that Chip's five-tone vocabulary cannot express. Next action: take these as one convergence PR after the cn() decision lands, not piecemeal. Found during PR-J adoption, 2026-08-03. | session 2026-08-03 (PR-J Wave 5, Builder B) | 2026-08-02 | +| #222 | P3 | task | Headers surface only partially converged in PR-J: mode-home-template and search-results-header-band untouched | VERIFIED CORRECT 2026-08-12 — re-checked against merged main and left open: Still unconverged: src/components/mode-home-template.tsx defines ModeHomeStatusNotice locally (:232) and imports neither PageHeader nor the DS EmptyState; search-results-header-band.tsx is likewise untouched. Note the adjacency — in-flight PR #1842 delegates ModeHomeStatusNotice to the DS EmptyState under #221, which is a different conversion from the PageHeader question this row asks. Re-check after #1842 merges. Builder A converged DsmPageHeader, InformationPageHeader and InformationPageBreadcrumbs onto PageHeader plus Breadcrumb, and declined two files with reasons. mode-home-template.tsx ModeHomeHero is a centred display hero on the fluid text-hero token and is the slot the in-flow phone composer sits in, so converging it onto a left-aligned PageHeader is a redesign of 13 mode homes that collides with the one-composer-per-page contract. search-results-header-band.tsx is a results spine carrying status, counts and filters, not a page-title stack, so its pin tests/search-results-header-band.dom.test.tsx remains unflipped. Both are defensible; both leave the headers surface partially adopted. Next action: decide whether either is in scope at all, or record them as permanently out of the PageHeader vocabulary. Found during PR-J adoption, 2026-08-03. | session 2026-08-03 (PR-J Wave 5, Builder A) | 2026-08-02 | | #231 | P1 | issue | Generation fallbacks no longer stick in answer cache; lithium generation quality still falls back safely | PARTIAL 2026-08-12: This PR fixes the clinically consequential stale-fallback path: every answer whose routing or degraded reason contains generation_fallback is excluded from rag_response_cache. Offline evidence: 96 focused answer-route tests and 574 RAG fixture/contract tests passed. Approved live baseline/final canaries preserved 36/36 document and content recall at 1.0 with zero per-case reciprocal-rank regressions; the final 44-case answer gate had zero citation or numeric-grounding failures. A budget extension was tested and rejected: four cache-bypassed 'Lithium dosing?' probes remained grounded, cited safe extractive fallbacks at 35-40 second candidate budgets; the decisive 40-second probe completed generation in 25.272 seconds and 27.237 seconds total with route_deadline_exceeded=false, but failed generation quality. Therefore OPENAI_ANSWER_TIMEOUT_MS and the route budget are not the current residual binding cause. Next: instrument and reproduce the structured generation-quality failure using provider-safe metadata, then make a separate bounded output-quality fix with an offline fixture and live canary. Stop: do not increase route/provider timeouts or cache any generation fallback. | session 2026-08-04 (production triage, live /api/search + /api/answer) | 2026-08-04 | -| #232 | P2 | issue | The PR-J clinical governance review record describes a head that was never merged | docs/branch-review-ledger.md records the clinical-governance review of PR #1595 (PR-J) at head f9f73c707. The head that actually merged was 590eb6cfb (squashed to f4448f8c1 on main). Three things landed after the reviewed head: the bot's answer-state projection changes, the two Codex review fixes, and the #228 attribution wording. The user passed the review knowing this, so the review decision itself is not in question - the record simply does not describe the merged tree, which defeats the ledger's purpose of proving what was reviewed. Next action: append a superseding record (never edit the existing row) with npm run ledger:append -- --supersede against the merged head, scoped to the delta between f9f73c707 and 590eb6cfb, or state explicitly that the delta was accepted unreviewed. Stop: the ledger is append-only - do not rewrite or delete the original row. | session 2026-08-04 (DS V2 Wave 5 close-out capture) | 2026-08-04 | -| #233 | P3 | task | COMPONENTS.md section 0 describes the pre-adoption world, and the optionality-marker contract change is undocumented | Two documentation debts left by PR-J, both in docs/design-system/COMPONENTS.md, naturally one PR. First: section 0's maturity matrix is stale. FormField, TextField, SearchField, Select, Checkbox, RadioGroup, PageHeader and Breadcrumb now have real product mounts, so 0.1 and 0.2 misdescribe what is registered versus built-but-unregistered, and 0.4's field-shell defects are closed by the five-control fold. A reader deciding whether a component is safe to adopt is reading the wrong answer. Second: FormField now marks only the requirement and leaves optional fields unmarked - (optional) was removed app-wide by design decision and is pinned by tests/ui-v2-form-field.dom.test.tsx - which is a design-system contract change that appears in no document. It belongs in COMPONENTS.md section 4 and probably DECISIONS.md. Next action: one docs PR updating section 0 from the actual mount list and recording the optionality rule with its rationale. Stop: do not re-add (optional) markers to satisfy a generic form-accessibility rule - the removal was deliberate and is test-pinned. | session 2026-08-04 (DS V2 Wave 5 close-out capture) | 2026-08-04 | -| #234 | P3 | task | answer-copy-payload.ts is the single clipboard payload builder for three surfaces and has no documentation | src/lib/answer-copy-payload.ts arrived in PR-J exporting answerStateForAnswer, buildAnswerClipboardText, resolveAnswerSources, citedSourcesOnly and singleDocumentClipboardMetadata. It is now the one place three product surfaces build a clipboard payload, which makes it a contract rather than a helper: a future caller that bypasses it can reintroduce the false-attribution defect the module exists to prevent (see #228). Nothing in docs/design-system mentions it. Next action: document the module and its five exports where the answer surface's copy contract is described, and state that new copy paths route through it rather than composing their own text. Found during PR-J close-out, 2026-08-04. | session 2026-08-04 (DS V2 Wave 5 close-out capture) | 2026-08-04 | -| #235 | P3 | task | ADOPTION.md section 7 proof shots exist for only four of the adopted surfaces | The adoption contract asks for a proof shot per adopted surface. The Wave 5 adoption captured four - DSM header, settings rows, patient panel, answer surface - and none for the forms fold, the catalogue and docs surfaces, the headers convergence, or the empty states adopted since. Section 7 therefore reads as complete while most of the adoption is unevidenced, which matters because the proof shot is what a later reader uses to tell an intended restyle from a regression (the #229 DSM eyebrow was almost rediscovered as a defect for exactly this reason). Next action: capture the missing shots against a warmed local server and attach them to section 7. Cheap and mechanical - no gate, no provider access. Stop: this is not the visual-baseline harness (#118) - do not commit Playwright snapshot PNGs or flip that job to blocking. | session 2026-08-04 (DS V2 Wave 5 close-out capture) | 2026-08-04 | -| #236 | P3 | task | Wave 5 left two orphan builder branches and three dev servers running | Housekeeping from the DS V2 wave. Branches claude/ds-v2-builder-a and claude/ds-v2-builder-b still exist locally with their worktrees removed; their content is inside the PR-J squash but squash merge means they are not ancestors of main, so ordinary merged-branch detection will not offer them for cleanup and a content check is required before deleting either. Dev servers were left running on port 3258 (Database-wt-ds-v2-capture), port 3135 (Database-wt-ds-v2-correctness) and port 3672 (Database-wt-ds-v2-empty-state-heading, this session). Next action: verify each branch is content-identical to what landed (git diff --stat origin/main branch must be empty) before deleting, and stop the servers whose worktrees are finished. Stop: do not delete a branch on ancestry alone - squash merge breaks ancestry, which is the trap this row exists to flag. | session 2026-08-04 (DS V2 Wave 5 close-out capture) | 2026-08-04 | -| #237 | P2 | rec | Eyeball low-confidence AccessibleTable densities at 320px before freezing Linux visual baselines | PR #1616 clinical MissingValue phrases increase text volume in sparse OCR grids. Contract forbids abbreviating to a dash. Next: open one real lowConfidence extraction at 320px phone width and accept or adjust dense preview column widths before committing Linux screenshots (#118). | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | -| #238 | P2 | rec | Visual pass for Sheet portal default on settings, sidebar, and answer overlays | PR #1616 flips Sheet portal default to true, moving ~10 product overlays into OverlayRoot. Token inheritance is safe; residual risk is ancestor-scoped CSS / contain / transform. Next: one visual pass of settings-dialog, ClinicalSidebar, answer-result sheets, launcher sheet, section-nav. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | +| #232 | P2 | issue | The PR-J clinical governance review record describes a head that was never merged | IN FLIGHT 2026-08-12 in PR #1835 (ledger doc hygiene + canonical reference cleanup). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. docs/branch-review-ledger.md records the clinical-governance review of PR #1595 (PR-J) at head f9f73c707. The head that actually merged was 590eb6cfb (squashed to f4448f8c1 on main). Three things landed after the reviewed head: the bot's answer-state projection changes, the two Codex review fixes, and the #228 attribution wording. The user passed the review knowing this, so the review decision itself is not in question - the record simply does not describe the merged tree, which defeats the ledger's purpose of proving what was reviewed. Next action: append a superseding record (never edit the existing row) with npm run ledger:append -- --supersede against the merged head, scoped to the delta between f9f73c707 and 590eb6cfb, or state explicitly that the delta was accepted unreviewed. Stop: the ledger is append-only - do not rewrite or delete the original row. | session 2026-08-04 (DS V2 Wave 5 close-out capture) | 2026-08-04 | +| #233 | P3 | task | COMPONENTS.md section 0 describes the pre-adoption world, and the optionality-marker contract change is undocumented | IN FLIGHT 2026-08-12 in PR #1842 (records DS adoption evidence and convergence state). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. Two documentation debts left by PR-J, both in docs/design-system/COMPONENTS.md, naturally one PR. First: section 0's maturity matrix is stale. FormField, TextField, SearchField, Select, Checkbox, RadioGroup, PageHeader and Breadcrumb now have real product mounts, so 0.1 and 0.2 misdescribe what is registered versus built-but-unregistered, and 0.4's field-shell defects are closed by the five-control fold. A reader deciding whether a component is safe to adopt is reading the wrong answer. Second: FormField now marks only the requirement and leaves optional fields unmarked - (optional) was removed app-wide by design decision and is pinned by tests/ui-v2-form-field.dom.test.tsx - which is a design-system contract change that appears in no document. It belongs in COMPONENTS.md section 4 and probably DECISIONS.md. Next action: one docs PR updating section 0 from the actual mount list and recording the optionality rule with its rationale. Stop: do not re-add (optional) markers to satisfy a generic form-accessibility rule - the removal was deliberate and is test-pinned. | session 2026-08-04 (DS V2 Wave 5 close-out capture) | 2026-08-04 | +| #234 | P3 | task | answer-copy-payload.ts is the single clipboard payload builder for three surfaces and has no documentation | IN FLIGHT 2026-08-12 in PR #1842 (publishes the answer-copy clipboard contract). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. src/lib/answer-copy-payload.ts arrived in PR-J exporting answerStateForAnswer, buildAnswerClipboardText, resolveAnswerSources, citedSourcesOnly and singleDocumentClipboardMetadata. It is now the one place three product surfaces build a clipboard payload, which makes it a contract rather than a helper: a future caller that bypasses it can reintroduce the false-attribution defect the module exists to prevent (see #228). Nothing in docs/design-system mentions it. Next action: document the module and its five exports where the answer surface's copy contract is described, and state that new copy paths route through it rather than composing their own text. Found during PR-J close-out, 2026-08-04. | session 2026-08-04 (DS V2 Wave 5 close-out capture) | 2026-08-04 | +| #235 | P3 | task | ADOPTION.md section 7 proof shots exist for only four of the adopted surfaces | IN FLIGHT 2026-08-12 in PR #1842 (records adoption evidence). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. The adoption contract asks for a proof shot per adopted surface. The Wave 5 adoption captured four - DSM header, settings rows, patient panel, answer surface - and none for the forms fold, the catalogue and docs surfaces, the headers convergence, or the empty states adopted since. Section 7 therefore reads as complete while most of the adoption is unevidenced, which matters because the proof shot is what a later reader uses to tell an intended restyle from a regression (the #229 DSM eyebrow was almost rediscovered as a defect for exactly this reason). Next action: capture the missing shots against a warmed local server and attach them to section 7. Cheap and mechanical - no gate, no provider access. Stop: this is not the visual-baseline harness (#118) - do not commit Playwright snapshot PNGs or flip that job to blocking. | session 2026-08-04 (DS V2 Wave 5 close-out capture) | 2026-08-04 | +| #236 | P3 | task | Wave 5 left two orphan builder branches and three dev servers running | Housekeeping from the DS V2 wave. Branches claude/ds-v2-builder-a and claude/ds-v2-builder-b still exist locally with their worktrees removed; their content is inside the PR-J squash but squash merge means they are not ancestors of main, so ordinary merged-branch detection will not offer them for cleanup and a content check is required before deleting either. Dev servers were left running on port 3258 (Database-wt-ds-v2-capture), port 3135 (Database-wt-ds-v2-correctness) and port 3672 (Database-wt-ds-v2-empty-state-heading, this session). Next action: verify each branch is content-identical to what landed (git diff --stat origin/main branch must be empty) before deleting, and stop the servers whose worktrees are finished. Stop: do not delete a branch on ancestry alone - squash merge breaks ancestry, which is the trap this row exists to flag. NOT VERIFIABLE FROM A CLOUD SESSION (checked 2026-08-12): this row describes machine-local git or process state. Claude Code on the web runs in a container cloned fresh at session start, so it reports 1 worktree, 0 upstream-less branches and no running dev servers no matter what is true on the machine that raised this. Do NOT close this row from a cloud session — that reading is an artifact of the container, not evidence. Verify from the originating machine. | session 2026-08-04 (DS V2 Wave 5 close-out capture) | 2026-08-04 | +| #237 | P2 | rec | Eyeball low-confidence AccessibleTable densities at 320px before freezing Linux visual baselines | IN FLIGHT 2026-08-12 in PR #1841 (renders empty dense cells wrapping rather than truncated, with a 320px jsdom assertion). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. PR #1616 clinical MissingValue phrases increase text volume in sparse OCR grids. Contract forbids abbreviating to a dash. Next: open one real lowConfidence extraction at 320px phone width and accept or adjust dense preview column widths before committing Linux screenshots (#118). | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | +| #238 | P2 | rec | Visual pass for Sheet portal default on settings, sidebar, and answer overlays | IN FLIGHT 2026-08-12 in PR #1842 (exercises the Sheet portal default and adds tests/sheet.dom.test.tsx). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. PR #1616 flips Sheet portal default to true, moving ~10 product overlays into OverlayRoot. Token inheritance is safe; residual risk is ancestor-scoped CSS / contain / transform. Next: one visual pass of settings-dialog, ClinicalSidebar, answer-result sheets, launcher sheet, section-nav. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | | #239 | P3 | rec | Manual phone rotation check for ResizeObserver-only phone chrome reserve | PR #1616 phone overlay reserve publishes only from ResizeObserver quiet-window deliveries. Desktop↔phone and late-mount recovery are covered; orientation that does not change stack height is a narrower trigger. Next: rotate a physical phone on a chrome-overlay route and confirm --phone-overlay-chrome-h updates. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | | #240 | P3 | rec | Confirm tooltip visual hard-clip asymmetry with design owner | Tooltip keeps overflow-hidden visual clamp while sr-only/aria-label retain full text. Design contract says supplementary-only. Next: design-owner confirmation that sighted users losing the clipped tail is acceptable, or allow overflow-y-auto for long clinical strings. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | -| #241 | P3 | rec | Therapy home summary count/slugs remain build-time; keep --check load-bearing | Home paints THERAPY_CATALOGUE_SUMMARY with catalogue I/O disabled. CI --check pins totalCount and defaultBrief/Sheet slugs. Next: do not bypass scripts/build-therapies-index.mjs --check; any home UI that lists real therapies must re-enable useTherapyData. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | -| #242 | P2 | task | Commit approved Linux visual baselines and promote adoption not-committed → committed | Baselines and provenance are DONE as of PR #1729 (branch claude/ds-adopt-visual-baselines): all six linux/ PNGs committed from ubuntu artifact visual-baseline-31251091603 (main @ bc33d414e), AWAITING_BASELINE emptied, and tests/__screenshots__/linux/provenance.json written with per-candidate SHA-256 + dimensions and an approved human review. Proven by that PR's own run: visual-junit tests=9 failures=0 skipped=0, and no visual-candidates/ directory, i.e. all six compared rather than skipped. REMAINING: only the surfaces flip to baseline.status committed. Blocked on ordering, measured 2026-08-08: validateLinuxVisualBaselineSet short-circuits on declaredPaths.length===0, so declaring files activates its rule that no non-allowlisted path may change since candidateSourceHead — and PR #1729 necessarily changed tests/design-system-adoption.test.ts, whose initialiseCandidateRepository seeded fixtures from the LIVE spec and so failed the moment AWAITING_BASELINE emptied. The two cannot land together. Next: after #1729 merges, re-capture candidates from a main run that already contains that fixture fix, then flip the surfaces against that head. Note this does not affect whether pixels compare — Playwright compares because the goldens exist on disk. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | -| #244 | P3 | rec | Forced-colours v2 mapping depends on grouped dark selectors staying in the media block | ckb-v2-tokens.css forced-colours block lists .ckb-v2.ckb-v2, .dark .ckb-v2.ckb-v2, and .ckb-v2.dark.ckb-v2 so specificity matches dark rules. Trimming to a single .ckb-v2.ckb-v2 silently drops dark HCM. Next: keep the contract test pin; never trim that selector group. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | -| #245 | P3 | rec | responsive-compact CrossModeLinks keeps duplicate rails in the DOM | Phone chip rail and md+ card rail both mount; display:none removes the inactive from the a11y tree. Tests/analytics counting role=link see doubles; cross-mode-links-rail is phone-only. Next: prefer the variant test ids; do not collapse to one rail with JS breakpoints (hydration risk). | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | +| #241 | P3 | rec | Therapy home summary count/slugs remain build-time; keep --check load-bearing | VERIFIED CORRECT 2026-08-12 — re-checked against merged main and left open: `check:therapy-data-index` runs `node scripts/build-therapies-index.mjs --check` (package.json:111) and is wired into verify:cheap:internal (:71), so the --check path is load-bearing exactly as this row asks. Standing caution, no action. Home paints THERAPY_CATALOGUE_SUMMARY with catalogue I/O disabled. CI --check pins totalCount and defaultBrief/Sheet slugs. Next: do not bypass scripts/build-therapies-index.mjs --check; any home UI that lists real therapies must re-enable useTherapyData. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | +| #242 | P2 | task | Commit approved Linux visual baselines and promote adoption not-committed → committed | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: Six linux/ PNGs are committed, but the adoption manifest still carries 68 `not-committed` entries — the surfaces flip is the remaining work, as stated. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. Baselines and provenance are DONE as of PR #1729 (branch claude/ds-adopt-visual-baselines): all six linux/ PNGs committed from ubuntu artifact visual-baseline-31251091603 (main @ bc33d414e), AWAITING_BASELINE emptied, and tests/__screenshots__/linux/provenance.json written with per-candidate SHA-256 + dimensions and an approved human review. Proven by that PR's own run: visual-junit tests=9 failures=0 skipped=0, and no visual-candidates/ directory, i.e. all six compared rather than skipped. REMAINING: only the surfaces flip to baseline.status committed. Blocked on ordering, measured 2026-08-08: validateLinuxVisualBaselineSet short-circuits on declaredPaths.length===0, so declaring files activates its rule that no non-allowlisted path may change since candidateSourceHead — and PR #1729 necessarily changed tests/design-system-adoption.test.ts, whose initialiseCandidateRepository seeded fixtures from the LIVE spec and so failed the moment AWAITING_BASELINE emptied. The two cannot land together. Next: after #1729 merges, re-capture candidates from a main run that already contains that fixture fix, then flip the surfaces against that head. Note this does not affect whether pixels compare — Playwright compares because the goldens exist on disk. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | +| #244 | P3 | rec | Forced-colours v2 mapping depends on grouped dark selectors staying in the media block | IN FLIGHT 2026-08-12 in PR #1841 (forced-colours selector specificity). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. ckb-v2-tokens.css forced-colours block lists .ckb-v2.ckb-v2, .dark .ckb-v2.ckb-v2, and .ckb-v2.dark.ckb-v2 so specificity matches dark rules. Trimming to a single .ckb-v2.ckb-v2 silently drops dark HCM. Next: keep the contract test pin; never trim that selector group. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | +| #245 | P3 | rec | responsive-compact CrossModeLinks keeps duplicate rails in the DOM | IN FLIGHT 2026-08-12 in PR #1842 (CrossModeLinks rail behaviour). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. Phone chip rail and md+ card rail both mount; display:none removes the inactive from the a11y tree. Tests/analytics counting role=link see doubles; cross-mode-links-rail is phone-only. Next: prefer the variant test ids; do not collapse to one rail with JS breakpoints (hydration risk). | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | | #248 | P2 | issue | Investigate why 20260705180000 search-health indexes were missing on live despite applied history | PR #1614 repairs the symptom with a mark-applied guard only. Confirm out-of-band whether the earlier reconcile migration partially applied, indexes were manually dropped, or schema_migrations was repaired — and whether check:drift should have caught this class before runtime search_schema_health. Renumbered from this PR's original #237 → #246 because main already used #237–#247 (PR #1616 findings plus the results-bar rows from PR #1615). | PR #1614 review / session 2026-08-05 (renumbered on main merge) | 2026-08-05 | -| #249 | P2 | rec | Extend issues/plan with an agent-safe wins classifier | **Outcome:** /issues and recommendation answers can filter agent-safe short wins (estimate ≤4h, Capability not Operator-only, no RAG/provider, not blocked on a human decision) without inventing a 35th skill. **Next:** add an optional filter/section to the issues skill (and plan/flightplan when useful) that lists those wins from the open table + queue; keep it as procedure on the existing skills unless the classifier is reused thrice — only then mint a thin wins alias. **Stop:** do not weaken acuity ordering; operator A1s still outrank engineering wins. | session 2026-08-05 fastest-wins plan | 2026-08-05 | -| #250 | P2 | task | Execute the fastest-wins multi-wave plan (Wave 0–4) | **Outcome:** the 2026-08-04/05 fastest-wins plan is executed with parallel agents, correct gates, and no regression. **Acuity first:** the recommended queue remains acuity-ordered — A1 rows (#059/#053/#231/#207/#226) are not demoted by wave numbers. Operator-track items (#022/#183) retain their A2 priority and approval gates. **A1 track (parallel, not a wave):** run approved A1 work (#059/#053/#231/#207/#226) whenever capability/approvals allow — do not wait for Waves 0–3. **Engineering waves only:** 0 = ledger/process gates (#201 resolved 2026-08-06; continue with remaining #202 work); 1A = #147+#176 phone CLS; 1B = #149+#167+#204+#210 gate integrity; 1C = hygiene/docs #232–#236+#223+#157+#151/#154/#187; 2 = #117 then #118; 3 = #098 then #189 (defer #099 body); 4 = remaining non-A1 clinical/UI packaging once approvals exist (never a holding pen for A1 items). **Next:** continue Wave 0/#202 on a fresh branch off origin/main in parallel with any approved A1 work; use gates + verification-router per PR; respect #155 concurrency. **Stop:** no RAG behaviour without flag+canary; no provider gates without approval; do not mix operationalRisk with clinical/UI in one squash. | session 2026-08-05 fastest-wins plan | 2026-08-05 | -| #251 | P3 | rec | Handoff checklist should pair gates skill with verification-router | **Outcome:** every PR handoff picks the smallest correct gate and pastes the decisive proof line, using verification-router when scope is unclear. **Next:** add one line to handoff/gates productivity defaults: after flightplan, run verification-router (or gates) before claiming green; never report exit 0 alone. **Stop:** do not stack verify:cheap + verify:ui + verify:release by default. | session 2026-08-05 fastest-wins plan | 2026-08-05 | +| #249 | P2 | rec | Extend issues/plan with an agent-safe wins classifier | IN FLIGHT 2026-08-12 in PR #1840 (wins classifier). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** /issues and recommendation answers can filter agent-safe short wins (estimate ≤4h, Capability not Operator-only, no RAG/provider, not blocked on a human decision) without inventing a 35th skill. **Next:** add an optional filter/section to the issues skill (and plan/flightplan when useful) that lists those wins from the open table + queue; keep it as procedure on the existing skills unless the classifier is reused thrice — only then mint a thin wins alias. **Stop:** do not weaken acuity ordering; operator A1s still outrank engineering wins. | session 2026-08-05 fastest-wins plan | 2026-08-05 | +| #250 | P2 | task | Execute the fastest-wins multi-wave plan (Wave 0–4) | SUPERSEDED IN LARGE PART 2026-08-12 — this row is a wave plan whose contents have been overtaken, and as written it now misdirects. Rows it names as live A1 work are CLOSED: #207 and #226 were archived on main, and #166 (its sibling in that cluster) archived in the 2026-08-12 sweep. Of its engineering waves: 1B's gate-integrity set is done (#149, #210 re-scoped, #204/#167 covered by in-flight PR #1837); 1C's hygiene set is largely done or in flight (#232, #151, #154, #187, #142, #156, #186 all now carry IN FLIGHT notes against PR #1835/#1836); Wave 0/#202 is in flight in PR #1840. What genuinely remains of the plan is Wave 1A (#147 phone CLS, still open and still reproducible offline at zero provider cost), Wave 2 (#117 then #118), and Wave 3 (#098 then #189). The A1 track is now just #059, #053 and #231 — two operator rows and one live investigation. Next: either re-cut this row against that much smaller remainder, or close it and let #147/#117/#118/#098/#189 stand on their own, which is probably the honest move now that the multi-agent framing has served its purpose. Stop unchanged: no RAG behaviour change without flag plus canary, no provider gates without approval, and do not mix operationalRisk with clinical or UI in one squash. | session 2026-08-05 fastest-wins plan | 2026-08-05 | +| #251 | P3 | rec | Handoff checklist should pair gates skill with verification-router | IN FLIGHT 2026-08-12 in PR #1840 (handoff checklist). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** every PR handoff picks the smallest correct gate and pastes the decisive proof line, using verification-router when scope is unclear. **Next:** add one line to handoff/gates productivity defaults: after flightplan, run verification-router (or gates) before claiming green; never report exit 0 alone. **Stop:** do not stack verify:cheap + verify:ui + verify:release by default. | session 2026-08-05 fastest-wins plan | 2026-08-05 | | #253 | P3 | task | #1606 needs a hand-merge against merged PR #1615, not a rebase | SUPERSEDED IN PART 2026-08-07: the component both PRs rewrite no longer exists. `MobileResultFilterControl` — the native `