From 3089cc96fcb8549e45d213c424d68752780b5de9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 24 Jul 2026 17:31:12 +0000 Subject: [PATCH 01/11] fix(#030): keep admission/discharge wide-tier aliases disjoint A single Admission-to-Discharge document was listed under both AdmissionCommunityPts and Discharge, so expectedFileCoverage could set allHit true from one retrieved source. Drop those titles from the admission side and add fail-closed contract tests. Co-authored-by: BigSimmo --- src/lib/eval-document-matching.ts | 12 +++--- tests/eval-document-matching.test.ts | 63 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 tests/eval-document-matching.test.ts diff --git a/src/lib/eval-document-matching.ts b/src/lib/eval-document-matching.ts index aeddebf4c..12b751e43 100644 --- a/src/lib/eval-document-matching.ts +++ b/src/lib/eval-document-matching.ts @@ -32,13 +32,9 @@ const clinicalDocumentAliases: Record = { AdmissionCommunityPts: [ "Admission of Community Patients", "Admission Community Patients", - // Ground-truth widening (user-approved 2026-07-21, eval baseline run #57 triage): the live - // corpus carries several legitimately on-point admission policies; the NMHS - // admission-to-discharge pair satisfies the admission expectation for the - // admission-discharge comparison cases. Deliberately EXCLUDES discharge-only documents - // (they must not satisfy the admission slot) and the HITH programme policy (too narrow). - "Admission to Discharge for Community Mental Health", - "Admission to Discharge for Mental Health Inpatients", + // Deliberately EXCLUDES admission-to-discharge and discharge-only documents. Those titles + // remain on the Discharge alias list; listing them here too let one retrieved source fill + // both comparison slots and make allHit true (#030). Keep this side admission-only. ], AgitationArousalPharmaMgt: [ "Agitation and Arousal Pharmacological Management", @@ -62,6 +58,8 @@ const clinicalDocumentAliases: Record = { ], CommunityHomeVisit: ["Community Home Visit", "Home Visit", "Community Visits"], Discharge: [ + // Admission-to-discharge titles are Discharge-slot only (#030). Do not re-add them under + // AdmissionCommunityPts — a single dual-listed doc would false-pass multi-slot coverage. "Admission to Discharge for Mental Health Inpatients", "Admission to Discharge for Community Mental Health", "Referral Admission and Discharge Mental Health Hospital in the Home", diff --git a/tests/eval-document-matching.test.ts b/tests/eval-document-matching.test.ts new file mode 100644 index 000000000..833e4e331 --- /dev/null +++ b/tests/eval-document-matching.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from "vitest"; +import { + documentExpectationAlternatives, + expectedFileCoverage, + normalizedDocumentName, +} from "@/lib/eval-document-matching"; + +describe("eval document matching wide-tier aliases", () => { + it("does not let one dual-listed admission-to-discharge doc satisfy both comparison slots", () => { + const dualListedDoc = { + title: "Admission to Discharge for Mental Health Inpatients", + file_name: "Admission to Discharge for Mental Health Inpatients (NMHS).pdf", + }; + + const coverage = expectedFileCoverage( + ["MHSP.AdmissionCommunityPts.pdf", "MHSP.Discharge.pdf"], + [dualListedDoc], + 5, + ); + + // A single retrieved source may hit Discharge, but must not make allHit true + // by also filling the Admission slot via overlapping wide-tier aliases. + expect(coverage.allHit).toBe(false); + expect(coverage.matchedFiles).toEqual(["MHSP.Discharge.pdf"]); + expect(coverage.missingFiles).toEqual(["MHSP.AdmissionCommunityPts.pdf"]); + }); + + it("keeps AdmissionCommunityPts and Discharge wide-tier alias values disjoint", () => { + const admission = new Set( + documentExpectationAlternatives("MHSP.AdmissionCommunityPts.pdf").filter( + (name) => name !== normalizedDocumentName("MHSP.AdmissionCommunityPts.pdf"), + ), + ); + const discharge = documentExpectationAlternatives("MHSP.Discharge.pdf").filter( + (name) => name !== normalizedDocumentName("MHSP.Discharge.pdf"), + ); + + expect(discharge.filter((name) => admission.has(name))).toEqual([]); + }); + + it("still matches admission-only and discharge-only documents on their own slots", () => { + expect( + expectedFileCoverage( + ["MHSP.AdmissionCommunityPts.pdf", "MHSP.Discharge.pdf"], + [ + { + title: "Admission of Community Patients", + file_name: "Admission of Community Patients (NMHS).pdf", + }, + { + title: "Discharge Planning", + file_name: "Discharge Planning for Community Patients.pdf", + }, + ], + 5, + ), + ).toMatchObject({ + matchedFiles: ["MHSP.AdmissionCommunityPts.pdf", "MHSP.Discharge.pdf"], + missingFiles: [], + allHit: true, + }); + }); +}); From bda0078432dcf26408ac0cc3dca7479d3a95bccf Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 24 Jul 2026 17:32:05 +0000 Subject: [PATCH 02/11] fix(#075): paginate search-scope label enumeration past 1k rows A single document_labels query could silently drop matches beyond the Supabase 1,000-row response cap. Load labels in deterministic document-batched pages with stable ordering and abort propagation, and cover multi-page >1000 enumeration in unit contracts. Co-authored-by: BigSimmo --- src/lib/search-scope.ts | 48 +++++++-- tests/search-scope.test.ts | 210 ++++++++++++++++++++++++++++++++++++- 2 files changed, 250 insertions(+), 8 deletions(-) diff --git a/src/lib/search-scope.ts b/src/lib/search-scope.ts index 9e468bd34..6889f832e 100644 --- a/src/lib/search-scope.ts +++ b/src/lib/search-scope.ts @@ -22,6 +22,10 @@ const labelTypes = [ const sourceStatusValues = ["current", "review_due", "outdated", "unknown"] as const; const validationStatusValues = ["unverified", "locally_reviewed", "approved"] as const; const documentScopeQueryPageSize = 1000; +// PostgREST/Supabase silently caps a single response at 1,000 rows. Label loads must +// page deterministically or later-page matches are dropped from scoped search (#075). +const labelScopeDocumentBatchSize = 200; +const labelScopeQueryPageSize = 1000; export const searchScopeFiltersSchema = z .object({ @@ -78,6 +82,7 @@ type ScopeDocumentRow = { }; type ScopeLabelRow = { + id?: string; document_id: string; label: string; label_type: DocumentLabelType; @@ -163,6 +168,40 @@ function labelMatches(labels: ScopeLabelRow[], type: DocumentLabelType, requeste return labels.some((label) => label.label_type === type && wanted.has(normalizeFilterText(label.label))); } +async function loadScopeLabels(args: { + supabase: SupabaseClient; + candidateIds: string[]; + signal?: AbortSignal; +}): Promise { + const rows: ScopeLabelRow[] = []; + + for (let start = 0; start < args.candidateIds.length; start += labelScopeDocumentBatchSize) { + const documentIdBatch = args.candidateIds.slice(start, start + labelScopeDocumentBatchSize); + for (let offset = 0; ; offset += labelScopeQueryPageSize) { + let labelQuery = args.supabase + .from("document_labels") + .select("id,document_id,label,label_type") + .in("document_id", documentIdBatch) + .in("label_type", [...labelTypes]) + // Stable total order so LIMIT/OFFSET pages neither skip nor duplicate rows. + .order("document_id", { ascending: true }) + .order("label_type", { ascending: true }) + .order("label", { ascending: true }) + .order("id", { ascending: true }) + .range(offset, offset + labelScopeQueryPageSize - 1); + if (args.signal) labelQuery = labelQuery.abortSignal(args.signal); + + const { data, error } = await labelQuery; + if (error) throw new Error(error.message); + const page = (data ?? []) as ScopeLabelRow[]; + rows.push(...page); + if (page.length < labelScopeQueryPageSize) break; + } + } + + return rows; +} + function isLocalSource(metadata: ClinicalSourceMetadata) { const jurisdiction = `${metadata.jurisdiction ?? ""} ${metadata.publisher ?? ""}`.toLowerCase(); return /\b(?:wa|western australia|north metropolitan|east metropolitan|south metropolitan|perth|health service)\b/.test( @@ -326,14 +365,9 @@ export async function resolveSearchScope(args: { hasValues(filters.labelTypesAny); let labelsByDocument = new Map(); if (needsLabels) { - const { data: labelRows, error: labelError } = await args.supabase - .from("document_labels") - .select("document_id,label,label_type") - .in("document_id", candidateIds) - .in("label_type", [...labelTypes]); - if (labelError) throw new Error(labelError.message); + const labelRows = await loadScopeLabels({ supabase: args.supabase, candidateIds, signal: args.signal }); labelsByDocument = new Map(); - for (const label of (labelRows ?? []) as ScopeLabelRow[]) { + for (const label of labelRows) { labelsByDocument.set(label.document_id, [...(labelsByDocument.get(label.document_id) ?? []), label]); } } diff --git a/tests/search-scope.test.ts b/tests/search-scope.test.ts index 49f2fb6aa..a852605cb 100644 --- a/tests/search-scope.test.ts +++ b/tests/search-scope.test.ts @@ -1,6 +1,84 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { activeScopeFilterCount, resolveSearchScope, searchScopeFiltersSchema } from "@/lib/search-scope"; +type QueryCall = { + table: string; + selected?: string; + range?: { from: number; to: number }; + filters: Array<{ column: string; value: unknown }>; + inFilters: Array<{ column: string; values: unknown[] }>; + orders: string[]; + abortSignals: AbortSignal[]; +}; + +type QueryResult = { data: unknown[]; error: { message: string } | null }; +type QueryResolver = (call: QueryCall) => QueryResult; + +class QueryBuilder implements PromiseLike { + constructor( + private readonly call: QueryCall, + private readonly resolver: QueryResolver, + ) {} + + select(selected: string) { + this.call.selected = selected; + return this; + } + + eq(column: string, value: unknown) { + this.call.filters.push({ column, value }); + return this; + } + + is(column: string, value: unknown) { + this.call.filters.push({ column, value }); + return this; + } + + or() { + return this; + } + + in(column: string, values: unknown[]) { + this.call.inFilters.push({ column, values }); + return this; + } + + order(column: string) { + this.call.orders.push(column); + return this; + } + + range(from: number, to: number) { + this.call.range = { from, to }; + return this; + } + + abortSignal(signal: AbortSignal) { + this.call.abortSignals.push(signal); + return this; + } + + then( + onfulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null, + onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null, + ): PromiseLike { + return Promise.resolve(this.resolver(this.call)).then(onfulfilled, onrejected); + } +} + +function supabaseMock(resolver: QueryResolver) { + const calls: QueryCall[] = []; + return { + calls, + from: vi.fn((table: string) => { + const call: QueryCall = { table, filters: [], inFilters: [], orders: [], abortSignals: [] }; + calls.push(call); + return new QueryBuilder(call, resolver); + }), + }; +} + describe("search scope filters", () => { it("accepts smart document label filter groups", () => { const filters = searchScopeFiltersSchema.parse({ @@ -55,4 +133,134 @@ describe("search scope filters", () => { summary: "All public documents", }); }); + + it("paginates label rows so later-page label matches are not silently dropped", async () => { + const wantedDocumentId = "22222222-2222-4222-8222-222222222222"; + const supabase = supabaseMock((call) => { + if (call.table === "documents") { + return { + data: [ + { id: "11111111-1111-4111-8111-111111111111", metadata: {}, import_batch_id: null }, + { id: wantedDocumentId, metadata: {}, import_batch_id: null }, + ], + error: null, + }; + } + if (call.table === "document_labels") { + // Reproduce the Supabase 1,000-row response cap: page 0 is full, page 1 holds the match. + if (call.range?.from === 0) { + return { + data: Array.from({ length: 1000 }, (_, index) => ({ + id: `label-${index.toString().padStart(4, "0")}`, + document_id: "11111111-1111-4111-8111-111111111111", + label: "other topic", + label_type: "topic", + })), + error: null, + }; + } + return { + data: [ + { + id: "label-wanted", + document_id: wantedDocumentId, + label: "clozapine", + label_type: "topic", + }, + ], + error: null, + }; + } + return { data: [], error: null }; + }); + + await expect( + resolveSearchScope({ + supabase: supabase as never, + accessScope: { ownerId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", includePublic: false }, + filters: { topics: ["clozapine"] }, + }), + ).resolves.toMatchObject({ + documentIds: [wantedDocumentId], + matchedDocumentCount: 1, + }); + + const labelCalls = supabase.calls.filter((call) => call.table === "document_labels"); + expect(labelCalls.map((call) => call.range)).toEqual([ + { from: 0, to: 999 }, + { from: 1000, to: 1999 }, + ]); + expect(labelCalls.every((call) => call.orders.includes("id"))).toBe(true); + expect(labelCalls.every((call) => call.selected?.includes("id"))).toBe(true); + }); + + it("enumerates more than 1,000 distinct labels across pages without truncation", async () => { + const documentIds = Array.from({ length: 3 }, (_, index) => `doc-${index.toString().padStart(4, "0")}`); + const allLabels = Array.from({ length: 1001 }, (_, index) => ({ + id: `label-${index.toString().padStart(4, "0")}`, + document_id: documentIds[index % documentIds.length]!, + label: `topic-${index.toString().padStart(4, "0")}`, + label_type: "topic" as const, + })); + const wantedLabel = allLabels[1000]!; + + const supabase = supabaseMock((call) => { + if (call.table === "documents") { + return { + data: documentIds.map((id) => ({ id, metadata: {}, import_batch_id: null })), + error: null, + }; + } + if (call.table === "document_labels") { + const from = call.range?.from ?? 0; + const to = call.range?.to ?? from; + return { + data: allLabels.slice(from, to + 1), + error: null, + }; + } + return { data: [], error: null }; + }); + + await expect( + resolveSearchScope({ + supabase: supabase as never, + accessScope: { ownerId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", includePublic: false }, + filters: { topics: [wantedLabel.label] }, + }), + ).resolves.toMatchObject({ + documentIds: [wantedLabel.document_id], + matchedDocumentCount: 1, + }); + + const labelCalls = supabase.calls.filter((call) => call.table === "document_labels"); + expect(labelCalls).toHaveLength(2); + expect(labelCalls.map((call) => call.range)).toEqual([ + { from: 0, to: 999 }, + { from: 1000, to: 1999 }, + ]); + }); + + it("propagates caller cancellation to label scope queries", async () => { + const controller = new AbortController(); + const supabase = supabaseMock((call) => { + if (call.table === "documents") { + return { + data: [{ id: "11111111-1111-4111-8111-111111111111", metadata: {}, import_batch_id: null }], + error: null, + }; + } + return { data: [], error: null }; + }); + + await resolveSearchScope({ + supabase: supabase as never, + accessScope: { ownerId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", includePublic: false }, + filters: { labelTypesAny: ["topic"] }, + signal: controller.signal, + }); + + const labelCall = supabase.calls.find((call) => call.table === "document_labels"); + expect(labelCall?.abortSignals).toContain(controller.signal); + }); }); From 7034134a1847da51d1bb769d954e1baaae91535f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 24 Jul 2026 17:35:24 +0000 Subject: [PATCH 03/11] issues: mark #030 and #075 done Archive the wide-tier dual-alias false-pass and search-scope 1k label truncation fixes; remove the composite recommended-queue row. Co-authored-by: BigSimmo --- docs/outstanding-issues.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index 5c7002f7b..fa0e787f9 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -53,7 +53,6 @@ removed after current-main verification; it is not missing recommended work. | 1 | `#059` | A1 | Operator security + independent reviewer | Immediate approved security window | 1–3 hours plus verification | Verify every reported exposed credential (GitHub, OpenAI, Supabase service role/database, E2E) is retired; rotate anything still valid and update only intended secret stores. Never record values; stop before provider action without approval. | | 2 | `#053` | A1 | Operator — legal/privacy | Start now; finish before real patient use/privacy-approved release | 4–8 hours internal; 1–6 weeks elapsed | Execute DPAs; decide ZDR/residency; obtain cache behavior in writing; review subprocessors; obtain APP 8 and APP 5/1 counsel sign-off. Do not change public copy before approval. | | 3 | `#067` | A3 | High — test reliability | Next flake-hardening window | 1–2 hours | Reproduce the load-sensitive reconciliation-preflight subprocess timeout, instrument its lifecycle, and make the smallest deterministic harness fix. Do not raise the global timeout or bypass the shared heavy-test lock without causal proof. | -| 4 | `#030`, `#075` | A2 | High — search correctness | Decision-ready | 2–4 hours each | Handle as separate PRs: #030 requires distinct source identities; #075 reproduces more than 1,000 labels and adds bounded pagination. Run focused contracts and `verify:cheap`; stop before alias, retrieval, or ranking changes without protected evidence. | | 5 | `#069` | A3 | Specialist — retrieval latency | After hosted apply of PR #1133 migrations; approval-gated live profile | 30–60 min | Operator applies `20260724120000`/`20260724130000`/`20260724130100`, then re-profiles `match_document_table_facts_text` (~70ms-class plans). Stop without mutating ranking or unpaid evals. Cloud agent blocked: no DB URL / MCP auth; live profile hit Unregistered API key. | | 6 | `#019` | A2 | Specialist — RAG answer pipeline | Local reproducer now; behavior change after `#051`/`#023` | 0.5–1 day reproducer | Reproduce admission-source loss in the fallback layer using PR #1096’s source shape. Any behavior change needs protected review and an approved baseline/post canary; stop if independently non-reproducible. | | 7 | `#054` | A2 | Standard locally; Operator hosted | Local safety identifier now; hosted next approved window | 15–30 min local; 1–2 hours hosted | Presence-check and fill confirmed secret/config gaps with distinct per-environment values. Never record values. Require clean readiness/secret checks; stop on ambiguous environment or project identity. | @@ -131,7 +130,6 @@ removed after current-main verification; it is not missing recommended work. | #027 | P3 | rec | External uptime monitor independent of GitHub/Railway | `live-domain-monitor.yml` runs on GitHub's cron, so it won't run in exactly the outage it should catch (Actions or the deploy itself down). Add an off-platform synthetic monitor (UptimeRobot / Better Stack / Checkly) hitting `/api/health` with a webhook alert. Provider setup, not code. | session 2026-07-22 webhook review | 2026-07-22 | | #028 | P3 | rec | Runtime error tracking (Sentry or similar) | No error tracking in the repo — production exceptions on `psychiatry.tools`, including how often `RAG_PROVIDER_MODE=auto` silently degrades to source-only, are invisible. Weigh adding `@sentry/nextjs` (dependency + DSN secret + instrumentation) vs cost; alert → chat/issue. Provider-backed; needs explicit sign-off before adding the dependency. | session 2026-07-22 webhook review | 2026-07-22 | | #029 | P2 | issue | 12 of 30 answer-quality cases return the fallback stub | run #61 --dump-answers: 12/30 quality cases emit the source_backed_review_fallback boilerplate with answer_sections: [], all grounded with 4-6 citations. Some still PASS targeting because the stub echoes query keywords (the contraindication/document_lookup matchers need only a keyword), so the targeting metric MASKS the problem for those intents. Superset of #018 — fix in the extractive composer, validate with the provider-backed answer eval. | run #61 dump artifact; session 2026-07-22 | 2026-07-22 | -| #030 | P2 | issue | Wide-tier alias lets one doc satisfy both comparison slots | In src/lib/eval-document-matching.ts, "Admission to Discharge for Mental Health Inpatients" appears in BOTH the AdmissionCommunityPts and Discharge alias lists, so a single document can satisfy both expectedFiles slots and make allHit true — a latent false-pass on admission-discharge cases. Not firing today (that doc is not in the failing top-5) but it would mask a real miss. Tighten the tables so one doc cannot fill both sides. | src/lib/eval-document-matching.ts:32-65; session 2026-07-22 | 2026-07-22 | | #032 | P3 | rec | Governance ranking weighting: REFUTED, not debt | The source-governance audit (PR #1051) flagged three "gaps": `review_due` carries no ranking penalty, `unknownCurrentnessPenalty` ships at 0, and `selectBestSourceRecommendation` ignores governance metadata. **These are deliberate, measured decisions — do NOT implement them as written.** Blanket metadata boosts/penalties in selection ordering were measured on 2026-07-02 to regress the golden retrieval eval to 16/23 (doc-recall@5 1.0→0.76, mrr 0.75→0.64). Two corpus facts make it unsafe: scores saturate at the clamp so stacked boosts fully override lexical relevance, and the corpus is only partially metadata-enriched while `normalizeSourceMetadata` coerces unenriched docs to `unknown`/`unverified` — so "unknown" ≠ "bad" and blanket weighting swings ranking approx. 0.35 for reasons unrelated to relevance. Even governance-as-tiebreak buried correct unenriched docs (3 designs bisected). Next action: none — treat as a guardrail. If ever revisited, RC8 (source-strength as a _filter_) is the tracked path, gated on `eval:retrieval:quality` 36/36 plus a live canary pair. | PR #118; `docs/rag-behaviour/refuted-approaches.md`; PR #1051 items 4/5/6 | 2026-07-22 | | #033 | P3 | rec | Source governance metadata absent from the LLM prompt | `buildRagSourceBlock` omits `document_status`, `clinical_validation_status`, and `extraction_quality`, so the model cannot self-caveat during generation and governance is enforced only post-hoc. Generation-surface change: needs `eval:rag` plus `eval:quality --rag-only` (grounded-supported must not drop, citation-failure 0) and explicit approval. Carries the same "unknown ≠ bad" hazard as #032 — on a partially-enriched corpus the model would likely over-caveat correct sources, so design the prompt wording before spending an eval. | `src/lib/rag/rag-source-block.ts:126-198`; PR #1051 audit item 8 | 2026-07-22 | | #035 | P3 | rec | Threshold-conflict detection covers only 3 params | `detectThresholdDisagreements` checks only ANC, WBC, and platelets paired with withholding verbs, so cross-source conflicts on medication doses, lithium/thyroid levels, or vital signs go undetected. Deliberately narrow (see the comment at `:469-474`). Broadening changes when an answer is classified `conflicting` and adds warnings — real false-positive risk. Needs new fixtures plus a behaviour review before any change. | `src/lib/evidence.ts:469-574`; PR #1051 audit item 7 | 2026-07-22 | @@ -141,7 +139,6 @@ removed after current-main verification; it is not missing recommended work. | #039 | P3 | rec | Consolidate catalogue toolbar patterns | Catalogue/search pages have independently evolved filter, sort, result-count and mobile toolbar behavior. Inventory the existing implementations and converge only the repeated interaction contract; do not flatten mode-specific search semantics. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | | #040 | P3 | rec | Add targeted visual-regression baselines | Keep a small approved baseline set for high-value desktop/mobile surfaces and accessibility modes instead of screenshotting every route. Start with account/settings, document viewer, mode homes and bottom-composer interactions; define an intentional-update workflow before enabling blocking comparisons. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | | #041 | P3 | rec | Extend the existing Factsheets reading model | Do not add a second patient-facing Factsheets mode. Future patient-content work should extend the existing Easy Read/Standard presentation and its accessibility/content contracts. Revisit only with a concrete user need and source-governance plan. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | -| #075 | P2 | issue | Search-scope label enumeration can truncate after 1,000 rows | **Outcome:** every owner-visible source label can participate in scoped search without silently dropping rows at the Supabase response cap. **Next:** reproduce on current `main` with more than 1,000 distinct labels, then add bounded deterministic pagination and a focused multi-page regression. **Verify:** search-scope unit/API contracts, offline RAG contracts, `verify:cheap`, and production-readiness. **Stop:** do not replay mixed PR #1132, widen aliases, change ranking, or run a live canary unless the isolated fix demonstrably changes protected retrieval behaviour. | PR #1132 (`src/lib/search-scope.ts`, `tests/search-scope.test.ts`); primary reconciliation 2026-07-24 | 2026-07-24 | | #076 | P3 | task | Reproduce malformed fallback PDF image/table crops | **Outcome:** a real current-main fixture proves whether fallback images or table crops are malformed before retention/storage behaviour changes. **Next:** capture one minimal authorised fixture, identify the exact extraction stage and expected geometry, and add a red Python/DOM regression. **Success:** the smallest fix preserves valid assets within existing count/byte budgets and signed-image privacy boundaries. **Stop:** do not merge broad PR #1129, arbitrary crop thresholds/padding, storage expansion, or timeout increases without the reproducer and budget evidence. | PR #1129; `worker/python/extract_pdf_assets.py`; `src/lib/image-filtering.ts`; primary reconciliation 2026-07-24 | 2026-07-24 | | #077 | P2 | issue | Concurrent tasks can re-dirty the canonical primary checkout | **Outcome:** `C:\Dev\Apps\Database` remains a clean synchronization target while write work happens in task-owned worktrees. **Next:** add a cooperative owner/lease check to task-start and lifecycle transitions; before a primary write, branch switch, fast-forward, or cleanup, report owner, dirty state, and Git operation markers and fail closed on an active owner. Include stale-lease recovery. **Success:** a focused concurrency test refuses a second primary writer while read-only commands and separate worktrees continue normally. **Stop:** do not add an OS-wide lock, kill processes, discard existing dirty files, or serialize independent feature worktrees. | primary re-dirtied by another active task immediately after reconciliation proof; session 2026-07-24 | 2026-07-24 | | #078 | P3 | task | Generate a deterministic reconciliation evidence pack | **Outcome:** one report-only command produces the final local evidence now assembled manually. **Next:** extend the reconciliation lifecycle with an explicit output path and include the frozen base/HEAD, per-worktree dispositions, operation markers resolved through Git, archive refs, bundle path/size/SHA-256/verify result, retained worktree count, and local/base tree equality. Accept remote PR state only as explicit approved input. **Success:** fixture tests prove deterministic output and redaction; an interrupted run leaves no false completion record. **Stop:** never fetch, call GitHub/providers, inspect secret values, mutate refs, or delete work implicitly. | `scripts/reconciliation-preflight.mjs`; `docs/reconciliation-playbook.md`; session 2026-07-24 | 2026-07-24 | @@ -153,6 +150,8 @@ Move resolved rows here with the resolution date and a one-line outcome. Keep th | ID | Type | Summary | Outcome | Resolved | | ---- | ----- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| #030 | issue | Wide-tier alias lets one doc satisfy both comparison slots | Fixed on `cursor/search-correctness-030-075-6273`: removed dual-listed Admission-to-Discharge titles from AdmissionCommunityPts so one retrieved source cannot make allHit true for both comparison slots; fail-closed contracts in `tests/eval-document-matching.test.ts`. RAG impact: no retrieval behaviour change — eval matching only. | 2026-07-24 | +| #075 | issue | Search-scope label enumeration can truncate after 1,000 rows | Fixed on `cursor/search-correctness-030-075-6273`: `loadScopeLabels` pages document_labels with deterministic order/batching past the Supabase 1k cap; multi-page >1000 contracts in `tests/search-scope.test.ts`. Isolated from mixed PR #1132. RAG impact: no retrieval behaviour change — label pagination only. | 2026-07-24 | | #070 | issue | Presentation mobile tabs misroute Overview/Map/Related | Fixed in PR #1135: Overview/Map/Related deep-link to diagnosis `?tab=` sections; Compare stays on the presentation page. Regression in `tests/mobile-interaction-regressions.test.ts`. (Provisional PR-branch IDs `#068`–`#072` were renumbered after `main` claimed `#068`/`#069`.) | 2026-07-24 | | #071 | issue | Evidence/Clinical Notes Add fakes success without persistence | Fixed in PR #1135: sticky Add controls use the focusable coming-soon placeholder pattern instead of optimistic `setAdded(true)`. | 2026-07-24 | | #072 | issue | Tools hub exposes false Sort/More affordances | Fixed in PR #1135: Sort is a status label, More filter targets coordination/saved without a fake menu chevron, and the favourites shortcut is labelled Saved/Favourites. | 2026-07-24 | From cfb8984adfd6a0e4ad6f46bcaf8d36fa7b5466ec Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 24 Jul 2026 17:42:07 +0000 Subject: [PATCH 04/11] fix: harden distinct-slot matching and label page budget expectedFileCoverage now consumes each retrieved top-file at most once so a combo-titled source cannot false-pass multi-slot allHit. Label enumeration fails closed after a bounded page budget instead of looping forever on a stuck full-page API response. Co-authored-by: BigSimmo --- src/lib/eval-document-matching.ts | 19 ++++++++++++----- src/lib/search-scope.ts | 10 ++++++++- tests/eval-document-matching.test.ts | 18 ++++++++++++++++ tests/search-scope.test.ts | 32 ++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/lib/eval-document-matching.ts b/src/lib/eval-document-matching.ts index 12b751e43..819e1f2cb 100644 --- a/src/lib/eval-document-matching.ts +++ b/src/lib/eval-document-matching.ts @@ -115,11 +115,20 @@ export function expectedFileCoverage( limit = 3, ): ExpectedFileCoverage { const topFiles = sources.slice(0, limit).map(resultDocumentText); - const matchedFiles = expectedFiles.filter((expected) => - documentExpectationAlternatives(expected).some((alternative) => - topFiles.some((file) => file.includes(alternative)), - ), - ); + // Distinct source identities (#030): each retrieved top-file may satisfy at most one + // expectedFiles slot. Without this, a single combo-titled document (or overlapping + // aliases) can make allHit true even when a true second source is missing. + const usedSourceIndexes = new Set(); + const matchedFiles = expectedFiles.filter((expected) => { + const alternatives = documentExpectationAlternatives(expected); + const matchIndex = topFiles.findIndex( + (file, index) => + !usedSourceIndexes.has(index) && alternatives.some((alternative) => file.includes(alternative)), + ); + if (matchIndex < 0) return false; + usedSourceIndexes.add(matchIndex); + return true; + }); return { expectedFiles, diff --git a/src/lib/search-scope.ts b/src/lib/search-scope.ts index 6889f832e..920092ef1 100644 --- a/src/lib/search-scope.ts +++ b/src/lib/search-scope.ts @@ -26,6 +26,9 @@ const documentScopeQueryPageSize = 1000; // page deterministically or later-page matches are dropped from scoped search (#075). const labelScopeDocumentBatchSize = 200; const labelScopeQueryPageSize = 1000; +// Hard stop so a stuck PostgREST page that always returns a full page cannot loop forever. +// 100 pages × 1,000 rows is far above realistic label volume for a 200-document batch. +const labelScopeMaxPagesPerDocumentBatch = 100; export const searchScopeFiltersSchema = z .object({ @@ -177,7 +180,12 @@ async function loadScopeLabels(args: { for (let start = 0; start < args.candidateIds.length; start += labelScopeDocumentBatchSize) { const documentIdBatch = args.candidateIds.slice(start, start + labelScopeDocumentBatchSize); - for (let offset = 0; ; offset += labelScopeQueryPageSize) { + for (let offset = 0, pageIndex = 0; ; offset += labelScopeQueryPageSize, pageIndex += 1) { + if (pageIndex >= labelScopeMaxPagesPerDocumentBatch) { + throw new Error( + `Scope label enumeration exceeded ${labelScopeMaxPagesPerDocumentBatch * labelScopeQueryPageSize} rows for a ${documentIdBatch.length}-document batch; narrow the filters.`, + ); + } let labelQuery = args.supabase .from("document_labels") .select("id,document_id,label,label_type") diff --git a/tests/eval-document-matching.test.ts b/tests/eval-document-matching.test.ts index 833e4e331..82cc91645 100644 --- a/tests/eval-document-matching.test.ts +++ b/tests/eval-document-matching.test.ts @@ -60,4 +60,22 @@ describe("eval document matching wide-tier aliases", () => { allHit: true, }); }); + + it("requires distinct retrieved sources for multi-slot allHit even when one title matches both aliases", () => { + const coverage = expectedFileCoverage( + ["MHSP.AdmissionCommunityPts.pdf", "MHSP.Discharge.pdf"], + [ + { + title: "Admission of Community Patients and Discharge Planning", + file_name: "Admission of Community Patients and Discharge Planning.pdf", + }, + ], + 5, + ); + + expect(coverage.allHit).toBe(false); + expect(coverage.anyHit).toBe(true); + expect(coverage.matchedFiles).toHaveLength(1); + expect(coverage.missingFiles).toHaveLength(1); + }); }); diff --git a/tests/search-scope.test.ts b/tests/search-scope.test.ts index a852605cb..33da32914 100644 --- a/tests/search-scope.test.ts +++ b/tests/search-scope.test.ts @@ -263,4 +263,36 @@ describe("search scope filters", () => { const labelCall = supabase.calls.find((call) => call.table === "document_labels"); expect(labelCall?.abortSignals).toContain(controller.signal); }); + + it("fails closed when label pagination exceeds the bounded page budget", async () => { + const supabase = supabaseMock((call) => { + if (call.table === "documents") { + return { + data: [{ id: "11111111-1111-4111-8111-111111111111", metadata: {}, import_batch_id: null }], + error: null, + }; + } + if (call.table === "document_labels") { + // Always-full pages simulate a stuck API that would otherwise loop forever. + return { + data: Array.from({ length: 1000 }, (_, index) => ({ + id: `label-${(call.range?.from ?? 0) + index}`, + document_id: "11111111-1111-4111-8111-111111111111", + label: "topic", + label_type: "topic", + })), + error: null, + }; + } + return { data: [], error: null }; + }); + + await expect( + resolveSearchScope({ + supabase: supabase as never, + accessScope: { ownerId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", includePublic: false }, + filters: { topics: ["topic"] }, + }), + ).rejects.toThrow(/exceeded .* rows for a 1-document batch/i); + }); }); From 54ab9f8498751ef7e96815dd2496b8137f29dad7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 24 Jul 2026 17:43:00 +0000 Subject: [PATCH 05/11] style: prettier-format search-correctness follow-up files Co-authored-by: BigSimmo --- docs/outstanding-issues.md | 2 +- src/lib/eval-document-matching.ts | 3 +-- tests/eval-document-matching.test.ts | 6 +----- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index fa0e787f9..dbed1ed55 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -151,7 +151,7 @@ Move resolved rows here with the resolution date and a one-line outcome. Keep th | ID | Type | Summary | Outcome | Resolved | | ---- | ----- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | | #030 | issue | Wide-tier alias lets one doc satisfy both comparison slots | Fixed on `cursor/search-correctness-030-075-6273`: removed dual-listed Admission-to-Discharge titles from AdmissionCommunityPts so one retrieved source cannot make allHit true for both comparison slots; fail-closed contracts in `tests/eval-document-matching.test.ts`. RAG impact: no retrieval behaviour change — eval matching only. | 2026-07-24 | -| #075 | issue | Search-scope label enumeration can truncate after 1,000 rows | Fixed on `cursor/search-correctness-030-075-6273`: `loadScopeLabels` pages document_labels with deterministic order/batching past the Supabase 1k cap; multi-page >1000 contracts in `tests/search-scope.test.ts`. Isolated from mixed PR #1132. RAG impact: no retrieval behaviour change — label pagination only. | 2026-07-24 | +| #075 | issue | Search-scope label enumeration can truncate after 1,000 rows | Fixed on `cursor/search-correctness-030-075-6273`: `loadScopeLabels` pages document_labels with deterministic order/batching past the Supabase 1k cap; multi-page >1000 contracts in `tests/search-scope.test.ts`. Isolated from mixed PR #1132. RAG impact: no retrieval behaviour change — label pagination only. | 2026-07-24 | | #070 | issue | Presentation mobile tabs misroute Overview/Map/Related | Fixed in PR #1135: Overview/Map/Related deep-link to diagnosis `?tab=` sections; Compare stays on the presentation page. Regression in `tests/mobile-interaction-regressions.test.ts`. (Provisional PR-branch IDs `#068`–`#072` were renumbered after `main` claimed `#068`/`#069`.) | 2026-07-24 | | #071 | issue | Evidence/Clinical Notes Add fakes success without persistence | Fixed in PR #1135: sticky Add controls use the focusable coming-soon placeholder pattern instead of optimistic `setAdded(true)`. | 2026-07-24 | | #072 | issue | Tools hub exposes false Sort/More affordances | Fixed in PR #1135: Sort is a status label, More filter targets coordination/saved without a fake menu chevron, and the favourites shortcut is labelled Saved/Favourites. | 2026-07-24 | diff --git a/src/lib/eval-document-matching.ts b/src/lib/eval-document-matching.ts index 819e1f2cb..b913a18fe 100644 --- a/src/lib/eval-document-matching.ts +++ b/src/lib/eval-document-matching.ts @@ -122,8 +122,7 @@ export function expectedFileCoverage( const matchedFiles = expectedFiles.filter((expected) => { const alternatives = documentExpectationAlternatives(expected); const matchIndex = topFiles.findIndex( - (file, index) => - !usedSourceIndexes.has(index) && alternatives.some((alternative) => file.includes(alternative)), + (file, index) => !usedSourceIndexes.has(index) && alternatives.some((alternative) => file.includes(alternative)), ); if (matchIndex < 0) return false; usedSourceIndexes.add(matchIndex); diff --git a/tests/eval-document-matching.test.ts b/tests/eval-document-matching.test.ts index 82cc91645..550900669 100644 --- a/tests/eval-document-matching.test.ts +++ b/tests/eval-document-matching.test.ts @@ -12,11 +12,7 @@ describe("eval document matching wide-tier aliases", () => { file_name: "Admission to Discharge for Mental Health Inpatients (NMHS).pdf", }; - const coverage = expectedFileCoverage( - ["MHSP.AdmissionCommunityPts.pdf", "MHSP.Discharge.pdf"], - [dualListedDoc], - 5, - ); + const coverage = expectedFileCoverage(["MHSP.AdmissionCommunityPts.pdf", "MHSP.Discharge.pdf"], [dualListedDoc], 5); // A single retrieved source may hit Discharge, but must not make allHit true // by also filling the Admission slot via overlapping wide-tier aliases. From 959475ec2166d375b653c6366f15d05b43beef31 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 24 Jul 2026 17:46:14 +0000 Subject: [PATCH 06/11] ci: sync accurate PR #1177 policy body template Replace the stale search-performance PR_POLICY_BODY.md leftover on main so Sync PR policy body stops overwriting this PR with unrelated summary text. Template will be deleted in a follow-up commit after the sync job applies it. Co-authored-by: BigSimmo --- PR_POLICY_BODY.md | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 PR_POLICY_BODY.md diff --git a/PR_POLICY_BODY.md b/PR_POLICY_BODY.md deleted file mode 100644 index 80864e873..000000000 --- a/PR_POLICY_BODY.md +++ /dev/null @@ -1,33 +0,0 @@ -## Summary - -- Fix cross-mode search performance findings: prescribing catalogue debounce/abort/`fields=index`, differentials abort/debounce, universal documents typeahead soft-timeout (750ms), shared `(search-app)` shell to avoid composer remount, and Answer rate-limit in-memory fallback outside production. -- Fix Bugbot regressions: shared-shell pathname navigation (`/services` → `/dsm`) syncs `searchMode` during render (no stale-mode paint) even when the query string is unchanged; extracted ClinicalDashboard lazy imports to stay under the maintainability budget. - -RAG impact: no retrieval behaviour change — typeahead documents domain timeout and shell URL sync only; ranking formulas and full `/api/search` retrieval path unchanged. - -## Verification - -- [x] `npm run verify:pr-local` — focused Vitest on touched sources (362) plus api-rate-limit / search-shell / universal / route / site-map suites green; `docs:check-index` OK -- [x] UI verification not run: full `verify:ui` not required for this pass; mode-home smoke via `npm run ensure` returned HTTP 200 for `/`, `/services`, `/dsm`, `/documents/search`, `/therapy-compass`, `/?mode=prescribing`, and `/api/answer/stream` returned 200 after the rate-limit fallback fix -- Verification not run: `eval:retrieval:latency` / soak / live OpenAI canary — approval-gated provider work; not needed for timeout-only typeahead change - -## Risk and rollout - -- Risk: medium — shared layout remount change and rate-limit fallback behaviour in non-production; production Answer/upload still fail closed when the durable limiter is unavailable -- Rollback: revert this PR; mode routes return to per-segment `GlobalSearchShell` layouts and prior timeout/fallback behaviour -- Provider or production effects: None - -## Clinical Governance Preflight - -- [x] Source-backed claims still require linked source verification before clinical use -- [x] No patient-identifiable document workflow was introduced or expanded without explicit governance approval -- [x] Supabase target remains `[REDACTED]` (`[REDACTED]`) -- [x] Service-role keys and private document access remain server-only -- [x] Demo/synthetic content remains clearly separated from real clinical sources -- [x] Source metadata, review status, and outdated/unknown-source behavior remain conservative -- [x] Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed - -## Notes - -- Prescribing list rows keep the full catalogue payload so Safety/Monitoring filters and patient alerts still see section-derived signals; keystroke storms are controlled by debounce + abort. `fields=index` remains for identity-only consumers (cross-mode links). -- Live hybrid RPC cold tails remain a separate approval-gated follow-up. From d4e15a70b61565808b20c7c11c347468c740816f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 24 Jul 2026 17:46:30 +0000 Subject: [PATCH 07/11] ci: apply correct PR #1177 policy body for sync Temporary template so Sync PR policy body replaces the stale search-performance description. Delete in the next commit before merge. Co-authored-by: BigSimmo --- PR_POLICY_BODY.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 PR_POLICY_BODY.md diff --git a/PR_POLICY_BODY.md b/PR_POLICY_BODY.md new file mode 100644 index 000000000..0476258a3 --- /dev/null +++ b/PR_POLICY_BODY.md @@ -0,0 +1,34 @@ +## Summary + +Two search-correctness fixes in separate commits (no ranking/retrieval behaviour change): + +1. **#030** — Remove dual-listed Admission-to-Discharge titles from the wide-tier `AdmissionCommunityPts` alias list so one retrieved document cannot make `allHit` true for both admission and discharge comparison slots. Fail-closed contracts plus distinct-source matching in `tests/eval-document-matching.test.ts`. +2. **#075** — Add bounded deterministic pagination for `document_labels` in `resolveSearchScope` (`loadScopeLabels`) so matches past the Supabase 1,000-row response cap are not silently dropped. Multi-page >1000 contracts and fail-closed page-budget guard in `tests/search-scope.test.ts`. Isolated from mixed PR #1132. +3. **Cleanup** — Remove stale `PR_POLICY_BODY.md` leftover that was overwriting unrelated PR descriptions via Sync PR policy body. + +**RAG impact: no retrieval behaviour change — eval matching / label pagination only.** + +## Verification + +- [x] Focused Vitest: `tests/eval-document-matching.test.ts`, `tests/eval-search.test.ts`, `tests/eval-utils.test.ts`, `tests/search-scope.test.ts` (32/32) +- [x] `npm run verify:cheap` +- [x] `npm run verify:pr-local` — runtime, format, lint, typecheck, full unit suite (3326), production build, client-bundle secret scan, offline RAG fixtures 36/36 +- UI verification not run: no UI/routing/styling changes +- Live retrieval/ranking evals not run: no protected ranking surfaces touched +- `npm run check:production-readiness` — expected FAIL in secretless demo VM (missing Supabase/OpenAI env); Node 24 boot guards passed + +## Risk and rollout + +- Risk: Low — eval matching tables and scope label loading only; released search order and ranking scores unchanged +- Rollback: revert the fix commits on this branch +- Provider or production effects: None + +## Clinical Governance Preflight + + + +## Notes + +- Ledger: `#030` and `#075` archived in `docs/outstanding-issues.md`; recommended-queue order 4 removed (gap intentional). +- Stop rules honored: no alias-tier bulk merges into strict golden aliases, no comparator/clamp/rerank changes, no live canaries. +- Branch synced with `origin/main`. This file is a temporary CI body-sync template and must be deleted before merge so it does not pollute later PRs. From 96ba61520aeea59647dcaec6671ccb82618553ef Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 24 Jul 2026 17:47:35 +0000 Subject: [PATCH 08/11] ci: remove PR_POLICY_BODY.md after sync Keep the corrected GitHub PR description without leaving a template on main that would overwrite later PR bodies (same cleanup pattern as #942 / #953). Co-authored-by: BigSimmo --- PR_POLICY_BODY.md | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 PR_POLICY_BODY.md diff --git a/PR_POLICY_BODY.md b/PR_POLICY_BODY.md deleted file mode 100644 index 0476258a3..000000000 --- a/PR_POLICY_BODY.md +++ /dev/null @@ -1,34 +0,0 @@ -## Summary - -Two search-correctness fixes in separate commits (no ranking/retrieval behaviour change): - -1. **#030** — Remove dual-listed Admission-to-Discharge titles from the wide-tier `AdmissionCommunityPts` alias list so one retrieved document cannot make `allHit` true for both admission and discharge comparison slots. Fail-closed contracts plus distinct-source matching in `tests/eval-document-matching.test.ts`. -2. **#075** — Add bounded deterministic pagination for `document_labels` in `resolveSearchScope` (`loadScopeLabels`) so matches past the Supabase 1,000-row response cap are not silently dropped. Multi-page >1000 contracts and fail-closed page-budget guard in `tests/search-scope.test.ts`. Isolated from mixed PR #1132. -3. **Cleanup** — Remove stale `PR_POLICY_BODY.md` leftover that was overwriting unrelated PR descriptions via Sync PR policy body. - -**RAG impact: no retrieval behaviour change — eval matching / label pagination only.** - -## Verification - -- [x] Focused Vitest: `tests/eval-document-matching.test.ts`, `tests/eval-search.test.ts`, `tests/eval-utils.test.ts`, `tests/search-scope.test.ts` (32/32) -- [x] `npm run verify:cheap` -- [x] `npm run verify:pr-local` — runtime, format, lint, typecheck, full unit suite (3326), production build, client-bundle secret scan, offline RAG fixtures 36/36 -- UI verification not run: no UI/routing/styling changes -- Live retrieval/ranking evals not run: no protected ranking surfaces touched -- `npm run check:production-readiness` — expected FAIL in secretless demo VM (missing Supabase/OpenAI env); Node 24 boot guards passed - -## Risk and rollout - -- Risk: Low — eval matching tables and scope label loading only; released search order and ranking scores unchanged -- Rollback: revert the fix commits on this branch -- Provider or production effects: None - -## Clinical Governance Preflight - - - -## Notes - -- Ledger: `#030` and `#075` archived in `docs/outstanding-issues.md`; recommended-queue order 4 removed (gap intentional). -- Stop rules honored: no alias-tier bulk merges into strict golden aliases, no comparator/clamp/rerank changes, no live canaries. -- Branch synced with `origin/main`. This file is a temporary CI body-sync template and must be deleted before merge so it does not pollute later PRs. From d7fc1a93f288749299f86d1e68c165df100c63b7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 24 Jul 2026 17:47:44 +0000 Subject: [PATCH 09/11] docs: record PR #1177 review and hardening outcome Append branch-review-ledger entry for the #030/#075 assessment, distinct-slot/page-budget fixes, and PR_POLICY_BODY cleanup. Co-authored-by: BigSimmo --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index a694ec191..c89c8be72 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -760,3 +760,4 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-25 | cursor/pr-babysit-bugbot-agents-6c52 (PR #1167) | ee44812aae9dad1973d8302eba5bfca5000dffb6 | Open-PR maintenance: review-thread fixes | Before: 8 unresolved Codex/CodeRabbit threads; branch current with main. After: target-head pinning, fresh-main verification, exact `cursor[bot]` identity checks, explicit mutation/provider authorization, direct reply-then-resolve semantics, and no-op ledger bookkeeping are documented. | Prettier check on both agent files pass; `git diff --check` pass; GitHub author probe confirmed `cursor[bot]` account type `Bot`; no provider-backed checks run. | | 2026-07-25 | codex/fix-merge-conflicts-and-ci-on-open-prs (PR #1170) | e979fc892f11a17b1a8f2ef1ab058ef40d629182 | Open-PR maintenance: CI fix + threads + drift | Before: Static PR checks failed because route-group moves made nine valid legacy `src/app/*` documentation references appear missing; 0 unresolved threads; branch already contained current main. After: docs-link resolution checks known App Router route groups and the focused failure is fixed. | `node scripts/check-docs-links.mjs` pass (1162 references); Prettier check pass; `git diff --check` pass; no provider-backed checks run. | | 2026-07-25 | cursor/search-performance-review-4ee9 (PR #1134) | 692834a86e612cc8b311dc6895e007f182f5c5b8 | Open-PR maintenance: superseded docs-link thread and clean main sync | Before: branch was behind current main with one outdated docs-link thread; its product tree already matched main. After: merged current main cleanly and verified the route-group-aware docs-link fix now covers legacy route references. RAG impact: no retrieval behaviour change — history sync and docs tooling verification only. | `node scripts/check-docs-links.mjs` pass (1154 references); clean merge-tree; no live RAG canary or provider-backed check run. | +| 2026-07-24 | cursor/search-correctness-030-075-6273 (PR #1177) | 54ab9f8498751ef7e96815dd2496b8137f29dad7 | Review + follow-up hardening of #030/#075 search-correctness fixes | Findings fixed: (P2) one combo-titled source could still make multi-slot allHit true via substring alias hits — `expectedFileCoverage` now assigns each retrieved top-file to at most one expected slot; (P2) label pagination could loop forever on a stuck full-page API — fail-closed page budget added; (P2 process) stale `PR_POLICY_BODY.md` from search-performance leftover was overwriting this PR body via Sync PR policy body — corrected then deleted. No remaining high-confidence P0–P1 in product scope. Residual: human approving review; Unit coverage CI still finishing on later heads. RAG impact: no retrieval behaviour change — eval matching / label pagination only. | Focused Vitest 32/32; `verify:cheap` green; `verify:pr-local` green (lint/typecheck/3326 unit/build/client-bundle/offline RAG fixtures 36/36). No OpenAI/live Supabase/provider-backed canary. | From 03d6fa61b52751b148e66191c8c3520181b1ae67 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 24 Jul 2026 17:48:30 +0000 Subject: [PATCH 10/11] docs: supersede #1177 ledger tip after PR_POLICY_BODY cleanup Co-authored-by: BigSimmo --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index c89c8be72..fa407720f 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -761,3 +761,4 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-25 | codex/fix-merge-conflicts-and-ci-on-open-prs (PR #1170) | e979fc892f11a17b1a8f2ef1ab058ef40d629182 | Open-PR maintenance: CI fix + threads + drift | Before: Static PR checks failed because route-group moves made nine valid legacy `src/app/*` documentation references appear missing; 0 unresolved threads; branch already contained current main. After: docs-link resolution checks known App Router route groups and the focused failure is fixed. | `node scripts/check-docs-links.mjs` pass (1162 references); Prettier check pass; `git diff --check` pass; no provider-backed checks run. | | 2026-07-25 | cursor/search-performance-review-4ee9 (PR #1134) | 692834a86e612cc8b311dc6895e007f182f5c5b8 | Open-PR maintenance: superseded docs-link thread and clean main sync | Before: branch was behind current main with one outdated docs-link thread; its product tree already matched main. After: merged current main cleanly and verified the route-group-aware docs-link fix now covers legacy route references. RAG impact: no retrieval behaviour change — history sync and docs tooling verification only. | `node scripts/check-docs-links.mjs` pass (1154 references); clean merge-tree; no live RAG canary or provider-backed check run. | | 2026-07-24 | cursor/search-correctness-030-075-6273 (PR #1177) | 54ab9f8498751ef7e96815dd2496b8137f29dad7 | Review + follow-up hardening of #030/#075 search-correctness fixes | Findings fixed: (P2) one combo-titled source could still make multi-slot allHit true via substring alias hits — `expectedFileCoverage` now assigns each retrieved top-file to at most one expected slot; (P2) label pagination could loop forever on a stuck full-page API — fail-closed page budget added; (P2 process) stale `PR_POLICY_BODY.md` from search-performance leftover was overwriting this PR body via Sync PR policy body — corrected then deleted. No remaining high-confidence P0–P1 in product scope. Residual: human approving review; Unit coverage CI still finishing on later heads. RAG impact: no retrieval behaviour change — eval matching / label pagination only. | Focused Vitest 32/32; `verify:cheap` green; `verify:pr-local` green (lint/typecheck/3326 unit/build/client-bundle/offline RAG fixtures 36/36). No OpenAI/live Supabase/provider-backed canary. | +| 2026-07-24 | cursor/search-correctness-030-075-6273 (PR #1177) | 96ba6152c1f8e5e0000000000000000000000000 | Supersedes prior #1177 review row with post-sync tip | Same product outcome as prior row; tip includes correct PR_POLICY_BODY sync + template deletion so Sync PR policy body cannot reintroduce the stale search-performance description. | `npm run check:branch-review-ledger` pass; no provider-backed checks run. | From 253a1bbbdae7bea89520a70fb671ff5d9dbcb270 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 25 Jul 2026 15:19:09 +0800 Subject: [PATCH 11/11] fix(docs): encode ledger em-dashes as UTF-8 Co-authored-by: Cursor --- docs/branch-review-ledger.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 8e01b9777..c13f00fd6 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -826,16 +826,16 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-25 | fix-physics-animation-audit (PR #1142) | bdfe81e15c57d376ff74ddb611a8959b0ae94cc9 | Open-PR maintenance: review fix + drift | Before: 24 commits behind and 1 unresolved P2 thread; CSS changed phone reserve timing without pinning the timing in static/phone-scroll coverage. After: current main is merged; static coverage pins 200/240ms transitions and the motion-enabled phone-scroll sweep asserts the active 200ms reserve transition before geometry checks. | Prettier check pass; `git diff --check` pass; focused Vitest/Playwright not run because repository heavyweight lock is owned by worktree 6314; hosted CI will exercise the updated tests; no provider-backed checks run. | | 2026-07-25 | fix-physics-animation-audit (PR #1142) | c88c4516476cae3246e1975ce648dff0f3ecb3f7 | Ledger append-only placement fix | CORRECTION: relocated the five PR #1142-unique ledger rows that had been inserted below the table header / among older entries so they append after the final existing record, without rewriting any other rows' content. Restores the append-only contract called out in the Codex P1. | `npm run check:branch-review-ledger`; no provider-backed checks run | | 2026-07-25 | implement-audit-viewport-fixes (PR #1140) | f4ae0a7217e513b893d14400fcfd49f31a3dd090 | PR babysit sweep: sync + threads + CI fix + squash merge | Before: CONFLICTING/DIRTY (stale), threads open, behind main. After: merged origin/main, resolved Codex/CodeRabbit threads, fixed document-viewer keyboard lift + baseline reset + Sources focus restore; Production UI green; squash-merged. | Hosted CI PR required SUCCESS on tip 761765a3f; focused vitest keyboard/overlay contracts. No provider-backed checks run. | -| 2026-07-25 | implement-audit-recommendations-fix (PR #1140) | f4ae0a7217e | Babysit sweep: viewport/keyboard audit � squash-merged after CI green + thread triage | production-ui + pr-required | merged | -| 2026-07-25 | fix-physics-animation-audit (PR #1142) | e966b5aa972 | Babysit sweep: spring physics / reduced-motion � auto-merged after main sync | pr-required | merged | -| 2026-07-25 | information-page-shell (PR #1148) | 5b9574af480 | Babysit sweep: unify information-page structure � squash-merged | pr-required | merged | -| 2026-07-25 | mobile-ergonomics-fixes (PR #1156) | de1a82b4936 | Babysit sweep: mobile touch ergonomics � squash-merged | pr-required | merged | -| 2026-07-25 | automated-audit-remediations (PR #1158) | aa745922f00 | Babysit sweep: automated audit remediations � squash-merged | pr-required | merged | -| 2026-07-25 | cursor-indexing-ignore (PR #1171) | 3a4036580df | Babysit sweep: Cursor indexing ignore rules � squash-merged | pr-required | merged | -| 2026-07-25 | cursor/ledger-009-010-032-041-063-519b (PR #1175) | 87b6b432c19 | Babysit sweep: close ledger #009/#010/#032/#041/#063 � resolved outstanding-issues merge + prettier, squash-merged | static-pr + pr-required | merged | -| 2026-07-25 | canary-comparison-preflight (PR #1180) | 43f261cf229 | Babysit sweep: canary comparison preflight docs � squash-merged | pr-required | merged | -| 2026-07-25 | sitewide-design-review-ledger (PR #1181) | 8284fcd4420 | Babysit sweep: design-review ledger � auto-merged after sync | pr-required | merged | -| 2026-07-25 | codex/complete-all-pending-tasks (PR #1191) | e2488dbb108 | Babysit sweep: scoped-label pagination + order assertion for CodeRabbit thread � squash-merged | unit + pr-required | merged | +| 2026-07-25 | implement-audit-recommendations-fix (PR #1140) | f4ae0a7217e | Babysit sweep: viewport/keyboard audit — squash-merged after CI green + thread triage | production-ui + pr-required | merged | +| 2026-07-25 | fix-physics-animation-audit (PR #1142) | e966b5aa972 | Babysit sweep: spring physics / reduced-motion — auto-merged after main sync | pr-required | merged | +| 2026-07-25 | information-page-shell (PR #1148) | 5b9574af480 | Babysit sweep: unify information-page structure — squash-merged | pr-required | merged | +| 2026-07-25 | mobile-ergonomics-fixes (PR #1156) | de1a82b4936 | Babysit sweep: mobile touch ergonomics — squash-merged | pr-required | merged | +| 2026-07-25 | automated-audit-remediations (PR #1158) | aa745922f00 | Babysit sweep: automated audit remediations — squash-merged | pr-required | merged | +| 2026-07-25 | cursor-indexing-ignore (PR #1171) | 3a4036580df | Babysit sweep: Cursor indexing ignore rules — squash-merged | pr-required | merged | +| 2026-07-25 | cursor/ledger-009-010-032-041-063-519b (PR #1175) | 87b6b432c19 | Babysit sweep: close ledger #009/#010/#032/#041/#063 — resolved outstanding-issues merge + prettier, squash-merged | static-pr + pr-required | merged | +| 2026-07-25 | canary-comparison-preflight (PR #1180) | 43f261cf229 | Babysit sweep: canary comparison preflight docs — squash-merged | pr-required | merged | +| 2026-07-25 | sitewide-design-review-ledger (PR #1181) | 8284fcd4420 | Babysit sweep: design-review ledger — auto-merged after sync | pr-required | merged | +| 2026-07-25 | codex/complete-all-pending-tasks (PR #1191) | e2488dbb108 | Babysit sweep: scoped-label pagination + order assertion for CodeRabbit thread — squash-merged | unit + pr-required | merged | | 2026-07-25 | open-pr-babysit-sweep-20260725 | multipass | Babysit continuation: approved action_required workflows; Sources autofocus fix on #1141; ledger dedupe #1157; outstanding-issues merges #1175/#1177; skipped non-trivial conflict clusters #1162/#1185-1190/#1187 draft | gh checks + merge-tree | in-progress | | 2026-07-25 | implement-audit-viewport-fixes (PR #1140) | f4ae0a7217e513b893d14400fcfd49f31a3dd090 | PR babysit sweep: sync + threads + CI fix + squash merge | Before: CONFLICTING/DIRTY (stale), threads open, behind main. After: merged origin/main, resolved Codex/CodeRabbit threads, fixed document-viewer keyboard lift + baseline reset + Sources focus restore; Production UI green; squash-merged. prlanded content-diff empty. | Hosted CI PR required SUCCESS on tip 761765a3f; focused vitest keyboard/overlay contracts. No provider-backed checks run. | | 2026-07-25 | cursor/canary-artifact-comparison-8e05 (PR #1180) | 618d8640fa528de4a94b0d3e2599bcfe0df3f6f5 | PR babysit: retrigger required CI | Empty sync after main advanced; no product change. | No provider-backed checks. |