Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions docs/phase-6-reaudit-2026-06-29.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
# Phase 6 Re-Audit - June 29, 2026

## Scope

This pass re-checked the remediation work after the M2 merge and live Supabase migration. It covered:

- clinical answer and source-governance refusal paths,
- private source access and signed URL ownership checks,
- reindex mutation safety and M2 abandoned-generation recovery,
- current indexing health,
- lower-confidence remediation candidates,
- broad DocumentViewer papercuts.

The pass intentionally avoided dependency changes and unrelated local work.

## Current Evidence

Commands run during the pass:

- `npm run check:supabase-project` - passed against project ref `sjrfecxgysukkwxsowpy`.
- `npm run check:production-readiness` - passed. Warnings were limited to the expected local `.env.local` service-role marker and missing top-level `.env`.
- `npm run reindex:health` - passed with 2065 indexed documents, 0 queued/processing/failed documents, 0 pending/processing/failed jobs, and 69334 chunks with retrieval synopsis.
- `npm run reindex:cleanup-staged` - dry run passed with 0 eligible abandoned staged-generation documents and 0 artifact rows.
- `npm run check:indexing` - passed with 2065/2065 documents indexed, 0 chunk-count mismatches, 0 mixed-generation documents, 0 duplicate content-hash groups, 0 chunks missing embeddings, and 0 actionable failed or stuck jobs.

Recent release context before this branch:

- PR #91 merged M2 atomic reindex recovery to `main`.
- The live migration `20260629000000_abandoned_reindex_generation_recovery.sql` was applied.
- `cleanup_abandoned_document_index_generations(uuid, integer, boolean)` exists in the live project; anon cannot execute it and service-role execution is available through the staged cleanup script.
- The post-M2 dry run found no abandoned staged generations to clean.

## Re-Audit Results

### Clinical and Security-Sensitive Paths

- `/api/answer` and `/api/answer/stream` still require authenticated users outside demo mode, apply rate limits, resolve scope before generation, and return an explicit safe refusal when source-governance warnings contain danger severity.
- The refusal contract does not spread the original generated answer, sources, smart panel, or API plan into the response.
- Document and image signed URL routes still require ownership-scoped document checks before returning private storage URLs.
- Image and document retrieval paths continue filtering uncommitted index-generation metadata so staged reindex artifacts do not leak into normal viewer/search paths.
- Single-document and bulk reindex routes still check ingestion mutation safety before mutating index artifacts.

No confirmed clinical/security regression was found in this pass.

### Lower-Confidence Batch

No lower-confidence audit item was promoted to implementation without a reproducible failing behavior or a failing test. The remaining candidates should stay backlog-scoped until each has:

- a concrete observed defect,
- an owner,
- acceptance criteria,
- a focused regression test.

### DocumentViewer Papercuts

One low-risk papercut was implemented:

- The indexed-source search hit controls now use compact icon controls with retained accessible names and `title` text. This reduces visual crowding in the tight source-passage toolbar without changing keyboard or screen-reader operation.

Existing coverage confirmed these broader DocumentViewer behaviors are already represented in smoke tests:

- mobile pinned evidence appears before the PDF preview,
- viewer section anchors expose Evidence and PDF,
- failed PDF preview exposes retry recovery,
- private missing source states do not get stuck on loading copy,
- full-document source search opens and advances between hits.

Broad DocumentViewer restyling remains deferred. It should be a separate UX PR if new defects are reproduced through screenshots or user sessions.

## Remaining Caveats

- The local working tree includes an unrelated edit in `scripts/classify-documents.ts`; this pass does not include or modify it.
- Production readiness still warns when local secret-bearing env files are present; this is expected for local verification and should not be committed.
- RAG enrichment and deep-memory coverage are not at 100 percent, but the current indexing health checks do not classify that as a release blocker.

## Follow-Up Acceptance Criteria

Before closing future deferred items:

- Add a failing test or screenshot evidence for each lower-confidence or broad viewer issue.
- Keep clinical/source-governance changes paired with `npm run check:production-readiness`.
- Keep DocumentViewer UI changes paired with the focused smoke path and `npm run verify:ui`.
- Keep Supabase/indexing changes paired with `npm run check:supabase-project`, `npm run check:indexing`, and `npm run reindex:health`.
8 changes: 4 additions & 4 deletions src/components/DocumentViewer.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -861,19 +861,19 @@ function IndexedTextPanel({
<button
type="button"
onClick={() => moveHit(-1)}
className={cn(secondaryButton, "min-h-8 px-2 text-xs")}
className={cn(secondaryButton, "min-h-9 min-w-9 justify-center p-0")}
aria-label="Previous document search hit"
title="Previous document search hit"
>
<ChevronLeft className="h-4 w-4" />
Prev
</button>
<button
type="button"
onClick={() => moveHit(1)}
className={cn(secondaryButton, "min-h-8 px-2 text-xs")}
className={cn(secondaryButton, "min-h-9 min-w-9 justify-center p-0")}
aria-label="Next document search hit"
title="Next document search hit"
>
Next
<ChevronRight className="h-4 w-4" />
</button>
</div>
Expand Down
9 changes: 7 additions & 2 deletions tests/ui-smoke.spec.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -1107,8 +1107,13 @@ test.describe("Clinical KB UI smoke coverage", () => {
await sourceSearch.fill("safety plan include");
const desktopTextPanel = page.getByTestId("desktop-chunk-indexed-text-panel");
await expect(desktopTextPanel.getByText("Hit 1 of 2").first()).toBeVisible();
await expect(desktopTextPanel.getByRole("button", { name: "Next document search hit" })).toBeVisible();
await desktopTextPanel.getByRole("button", { name: "Next document search hit" }).click();
const previousHit = desktopTextPanel.getByRole("button", { name: "Previous document search hit" });
const nextHit = desktopTextPanel.getByRole("button", { name: "Next document search hit" });
await expect(previousHit).toHaveAttribute("title", "Previous document search hit");
await expect(previousHit).toHaveText("");
await expect(nextHit).toHaveAttribute("title", "Next document search hit");
await expect(nextHit).toHaveText("");
await nextHit.click();
await expect(desktopTextPanel.getByText("Hit 2 of 2")).toBeVisible();
await expect(desktopTextPanel.locator("mark").filter({ hasText: "safety" }).first()).toBeVisible();
await expectNoPageHorizontalOverflow(page);
Expand Down