Skip to content

audit fixes (P10): Privacy and governance documents - #2622

Merged
BigSimmo merged 18 commits into
mainfrom
claude/audit-fix-p10
Sep 4, 2026
Merged

audit fixes (P10): Privacy and governance documents#2622
BigSimmo merged 18 commits into
mainfrom
claude/audit-fix-p10

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Summary

Audit remediation package P10 — Privacy and governance documents, from docs/audit/full-repository-audit-2026-09-02.md (PR #2573). Findings fixed: M7, M2, L103, L104, L7, L4. Each fix landed test-first as its own commit.

  • b66395f privacy: describe the shared public corpus on /privacy (M7)
  • 955cd98 docs: record browser answer-thread and recent-query storage in the PIA (M2)
  • 23296b0 docs: correct the retention attestation for the 2026-09-01 schedule migration (L103)
  • 678e6f8 docs: label the unmeasured SLO thresholds in observability-slos (L104)
  • 3e04b2e calculators: label the internal governance record as provenance, not a clinical source (L7)
  • fa624a3 care-plan: record a re-verification cadence for the real WA crisis numbers (L4)
  • 3efba0a privacy: state that public-corpus files also mint signed links (M7, follow-up)

Findings in this package not fixed here, with reasons:

  • L5 — Cannot be completed inside the owned files. public/brand/preview.html is owned, but tests/developer-hub-panels.test.ts asserts it is byte-identical to docs/brand/preview.html (not owned, not generated by any script), so editing only the owned copy would fail that test, and weakening a test is prohibited. The X-Robots-Tag noindex header for /brand/:path* also lives in next.config.ts (not owned). Exact change for the owner, applied identically to both HTML copies: delete the fonts.googleapis.com and fonts.gstatic.com preconnect links and the Google Fonts stylesheet link (the proxy CSP already blocks the stylesheet, so the page already renders in fallback fonts), and add <meta name="robots" content="noindex, nofollow">.
  • L103 (readiness-register half) — The PIA half is fixed. The privacy readiness register entry whose externalEvidenceReference still describes migration 20260901033250 as a staging-only change is outside the package's owned file list, so it was left untouched.

Decisions needed from the owner:

  • L5: apply the fonts/noindex change to both public/brand/preview.html and docs/brand/preview.html (byte-identity is pinned by tests/developer-hub-panels.test.ts), and decide whether to add /brand/:path* to the X-Robots-Tag noindex block in next.config.ts.
  • L103: amend the privacy readiness register's externalEvidenceReference so it no longer describes migration 20260901033250 as staging-only, and cite the post-merge live-drift run for the merge that applied it.
  • L103: if a current job-id attestation is wanted, an operator must run the provider-backed live cron.job read (confirmation required); the PIA now deliberately records job names and schedules instead.
  • M7: confirm the reworded /privacy access section is acceptable to the pending privacy-adviser review (PRIV-LEGAL-APP1-APP5-NOTICE), since it now states publicly that the shared corpus is searchable without signing in.
  • L104: decide whether to wire latency p95, source-gap rate and unsupported rate into answerSloSnapshot with their own OPS_* codes, or leave them as the manual-SQL targets they are now labelled.

RAG impact: none

Verification

  • npm run verify:pr-local — result: - completed: check:runtime, check:installed-lock-parity, format:changed, check:diff-integrity, sitemap:check, check:repo-awareness-snapshot, docs:check-index, docs:check-inventory, docs:check-scripts, docs:check-links, check:branch-review-ledger, check:outstanding-issues, check:ledger-write-discipline, lint, typecheck, test, build, check:rag:fixtures, check:medication-interactions, check:medication-lexicon-report · - failed: (none) · - not reached: (none) · Test Files 1127 passed | 1 skipped (1128) · Tests 14964 passed | 2 expected fail | 3 skipped (14969) (the gate runner recorded exit code 0)
  • npm run check:diff-integrity[diff-integrity] PASS — 2 changed test file(s), 22 -> 24 test case(s), against base 51ddfcd83.
  • Package gate notes: npm run verify:pr-local (heavy)

Verification not run: npm run verify:ui — browser proof left to CI (the pinned Chromium is not installed in this container); no narrowed browser run is claimed as the full gate.
Verification not run: npm run verify:release — no release or handoff confidence is claimed.
Verification not run: provider-backed gates (check:production-readiness, every eval:*) — nothing here touches OpenAI, Supabase, Railway or Sentry at run time; all work was offline.

Risk and rollout

  • Risk: medium — privacy-facing copy and one presentation-only clinical-reference change; the two behaviour-visible changes (M7, L7) are each pinned by a new test that was confirmed failing before its fix. Residual risk stated by the implementer: five of the six changes are copy or documentation only and cannot alter runtime behaviour; the one code change (L7) is presentation-only and makes citation more conservative, not less — the governance record is still linked, just under its own "Governance reference:" label, and no scoring, ranking, retrieval or source-governance logic was touched. RAG impact: none. The main residual risk is accuracy of the new claims rather than breakage: the M7 rewording asserts a two-tier access model verified statically from the promotion migrations, owner-scope.ts and the /api/search public path but not confirmed against the live database, and the L103 note states that merging 20260901033250 re-created the pg_cron jobs on production, as the merge-deploys-within-seconds rule implies — no replacement job id was recorded, because reading live cron.job is provider-backed. L104's automation labels were derived from the complete OPS_* code list in scripts/lib/operational-alerts.mjs, so if alerting exists elsewhere (host-native or Sentry) those labels understate coverage. L4 changed comments only, so a stale crisis number still fails silently after the stated 2027-08-20 due date; the finding's suggested aging assertion was not added because no care-plan test file is in this package's owned list.
  • Rollback: revert this PR's commits; each commit is one finding and reverts independently.
  • Provider or production effects: None.
  • RAG impact: none

Clinical Governance Preflight

Each item confirmed against this package's diff (findings M7, M2, L103, L104, L7, L4). The checked line is the exact policy item; the note beneath it is the evidence.

  • Source-backed claims still require linked source verification before clinical use
    no change to source verification or citation requirements.
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
    none introduced or expanded.
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
    no Supabase env value, migration target or configured project changes.
  • Service-role keys and private document access remain server-only
    no client exposure of service-role credentials; private access paths unchanged or tightened.
  • Demo/synthetic content remains clearly separated from real clinical sources
    unchanged.
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
    unchanged or made more conservative.
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed
    reviewed; the changes correct reference rendering defects the 2026-09-02 audit recorded and do not add decision-support behaviour.

Notes

  • Files touched: src/lib/privacy-page-content.tsx, tests/privacy-page.dom.test.tsx, docs/privacy-impact-assessment.md, docs/observability-slos.md, src/components/calculators/calculator-evidence.ts, src/components/calculators/search-detail.tsx, tests/calculators-clinical-safety.dom.test.tsx, src/components/care-plan/mockups/fixtures.ts, docs/care-plan/patient-facing-sheets/README.md, docs/design-system/adoption.md (regenerated by the pre-commit hook on the M7, L7 and L4 commits)
  • Review status: this package's round-1 adversarial review never completed (its workflow stopped), unlike the other packages in this programme. An independent clinical-governance review of the whole diff was run before publication; its verdict is posted as a comment on this PR. Treat that comment, not this line, as the review record.
  • One owner per file across the remediation programme: this package's files are edited by no other open remediation PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DHSyfuC6mS98ystWFiitAR


Generated by Claude Code


Note

Medium Risk
Medium risk from privacy-facing copy that asserts public corpus access and signed-link behavior, plus calculator citation labeling; changes are mostly documentation with two UI/copy paths covered by new tests and no retrieval or clinical scoring changes.

Overview
Audit remediation P10 aligns privacy/governance docs and user-facing copy with how the product actually behaves, without changing RAG or scoring logic.

Privacy and PIA: /privacy now states a two-tier access model—operator-published shared public corpus (searchable including without sign-in) versus owner-scoped uploads and logs—and that signed URLs can cover public-corpus files. The PIA adds browser sessionStorage (12-hour answer threads, recent queries) to data classification and retention, documents stale pg_cron job ids after migration 20260901033250, and expands shared-workstation residual risk.

Observability docs: docs/observability-slos.md clarifies that only hybrid-RPC error rate and degraded answer rate are repo-computed and alerted (answerSloSnapshot / OPS_*); latency p95, source-gap, and unsupported thresholds are manual SQL targets, not monitoring. The deep health probe is documented as returning up to four blocks including spend.

Calculators (presentation): partitionEvidenceSources splits internal_governance_record from clinical validation sources; Score and clinical-considerations panels show “Governance reference:” separately while keeping links—pinned by a new DOM test.

Care-plan prototype: Docs and fixture comments add a 12-month re-verification cadence (next due 20 Aug 2027) for real WA crisis numbers in fixtures.ts, with correction only in fixtures—not rendered sheets.

Reviewed by Cursor Bugbot for commit c40cfd0. Configure here.

The "Who can access your data" section claimed "There is no shared
corpus across accounts: another signed-in user cannot search, retrieve,
or cite your uploads." Committed migrations promote indexed documents to
the null-owner public corpus, resolveRetrievalAccessScope always sets
includePublic, and /api/search serves unauthenticated callers as
publicOnly - so published documents are searchable, retrievable and
citable by every account and by signed-out visitors.

Trigger: any reader of the /privacy notice, for any document published
into the public corpus.

The section now states both tiers (shared public corpus vs owner-scoped
uploads under row-level security) and its gist matches. Proven by a new
case in tests/privacy-page.dom.test.tsx, "describes the shared public
corpus alongside owner-scoped uploads", which renders the page and
asserts the removed claim is gone and both tiers are described.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t
…A (M2)

The privacy impact assessment's data inventory had no row for the
browser-side persistence of raw clinical query text. answer-thread-storage
writes the raw query, the generated answer and the source excerpts for up
to 12 turns (4.5 MB cap, 12-hour TTL) to window.sessionStorage under an
owner-scoped key that signed-out visitors share as guest-tab-session, and
recent-query-storage keeps the last five raw queries per owner. The PIA's
only browser-storage statement covered Safety Plan content.

Trigger: any completed answer in the dashboard; the thread stays
restorable in that tab for 12 hours.

Adds a section 2 inventory row and a section 6 retention row plus a
"Browser-side retention" note giving the TTL, the size cap, the clearing
paths, the shared-workstation residual risk, and the tests that pin the
behaviour, so a future move to localStorage reads as a PIA deviation.
Documentation only; no test added because no behaviour changed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t
…igration (L103)

The PIA presented pg_cron job ids 11, 12, 13 and 16 as current retention
evidence, and the migration that superseded them is named
"enable_staging_privacy_retention_schedules". It ships in
supabase/migrations, so merging it to main applied it to the live clinical
database too: it unschedules the five named purge jobs and re-schedules
four, giving them new job ids on production.

Trigger: any reader treating the quoted job ids as the current APP 11
retention control, or the file name as proof the change was staging-only.

Section 6 now marks the jobs active by name, labels the 2026-07-06
verification block as superseded, and adds a dated note that the migration
re-created the jobs on production as well as staging, points at the
post-merge live-drift run as the application gate, and records no
replacement job id because re-reading it is provider-backed. Documentation
only; no test added because no behaviour changed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t
Section 2 published warn/page thresholds for answer latency p95 by route
mode, /api/search latency, source-gap rate and unsupported rate as if they
were alerted. Nothing in the repository computes a latency percentile, a
source-gap rate or an unsupported rate, and the ops digest has no alert
code for any of them - its complete code list covers only the hybrid-RPC
error rate, the degraded-answer rate, projected spend and canary staleness.

Trigger: an operator reading a "page" row as monitoring coverage; a latency
or unsupported-rate regression would stay invisible until someone hand-ran
the SQL, which is the silent-degradation failure mode the document exists
to close.

Adds an automation-status note naming the two alerted objectives and their
OPS_* codes, labels the three unmeasured sections "Manual SQL - not
alerted", and corrects the deep-probe block count from three to four by
documenting the spend block. Documentation only; no test added because no
behaviour changed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t
…a clinical source (L7)

The calculator evidence registry attaches source:governance - an
internal_governance_record pointing at a repository Markdown spec - to
every active instrument's sourceIds, and both the score panel and each
clinical consideration rendered it inside the same "Sources:" line as a
validation study, hyperlinked to a GitHub blob URL.

Trigger: opening any calculator detail on /calculators; a clinician saw a
release-decision record presented as clinical evidence beside the PHQ-9
validation study.

Adds partitionEvidenceSources to calculator-evidence.ts and renders the
governance record on its own "Governance reference:" line, keeping the
link. Proven by a new case in tests/calculators-clinical-safety.dom.test.tsx,
"separates the internal governance record from the clinical sources line",
which asserts the Sources line no longer carries the record and the
labelled line still links it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t
…mbers (L4)

publicCrisisContacts holds the only non-fictional data in the care-plan
prototype - MHERL Perth 1300 555 788, MHERL Peel 1800 676 822 and
Rurallink 1800 552 002 - each stamped verifiedOn 2026-08-20. Nothing in
the repository ages that date, while the same file models CMHT contacts as
review_due after months.

Trigger: time passing. A redirected or retired crisis number would keep
printing on the Personal Safety Plan and Patient Plan sheets, the one place
the prototype's synthetic framing does not protect the reader.

Adds an explicit 12-month cadence (next due 2026-08-20 plus one year) and a
re-use precondition beside the values in fixtures.ts, and the same cadence
in the patient-facing-sheets README that lists the numbers. The numbers,
the verifiedOn dates and the rendered output are unchanged, so no test
changed; tests/care-plan-linked-routes.dom.test.tsx still passes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t
@supabase

supabase Bot commented Sep 4, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 089e5cb7-5693-453a-94fe-5350f9146541


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…aims (review follow-up)

Four accuracy corrections to the user-facing /privacy page, verified against
the code they describe:

- The shared corpus is now stated as intent, not enforced fact. Nothing in the
  application inspects document content, and docs/privacy-impact-assessment.md
  section 2 classifies uploaded documents as possibly carrying PHI.
- Publication is no longer implied to be only the per-document, approval-gated
  path. set_document_corpus_access_mode('public')
  (supabase/migrations/20260825025032_reversible_document_corpus_access_mode.sql)
  snapshots every row in public.documents, disables the
  documents_require_publication_approval trigger, and publishes all of them at
  once, bypassing document_publication_approvals.
- Owner scoping is attributed to the application tier first. The app reads
  through the service-role admin client and enforces ownership in code
  (withOwnerReadScope in src/lib/public-api-access.ts); the authenticated-role
  RLS policies stand behind that for direct database access. See
  docs/audit/tenancy-defense-in-depth-review.md section 2.
- "Your uploads" is corrected for a non-administrator reader. POST /api/upload
  requires { administrator: true } (src/app/api/upload/route.ts:116).

Test assertions in tests/privacy-page.dom.test.tsx and tests/privacy-ui.test.ts
are re-pinned to the new wording; four new cases pin each corrected claim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHSyfuC6mS98ystWFiitAR
…n the PIA (review follow-up)

Four accuracy corrections, each verified against the code or workflow it
describes:

- Section 1 "query redaction" now says raw query text is not persisted
  *server-side* by default, and points at the section 2 browser row and the
  section 6 browser-side retention note, which record raw query text and the
  generated answer held in sessionStorage for up to 12 hours.
- The "verified running on live (both jobs active = true)" bullet now carries
  its date and the fact that the job ids were re-issued on 2026-09-01, so the
  reader is sent to section 6 rather than treating a 2026-07-06 read as current.
- The post-merge live-drift workflow is no longer named as the schema-
  application gate for the pg_cron purge jobs. scripts/check-drift.ts compares
  extensions, tables, views, functions, indexes, policies, constraints,
  triggers and storage buckets only; cron.job rows are outside that inventory,
  which the repository audit records as finding M23. The only proof is an
  operator `select jobname, schedule from cron.job` read. The four section 6
  table cells drop the "Active" assertion accordingly.
- Adds PIA-8 (Low-Medium) to the section 10 gap register and the section 1
  summary table: the browser answer thread retains raw query text for 12 h,
  which section 6 already named as the residual control question but which the
  register did not carry.

No clinical number, retention period, or schedule is changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHSyfuC6mS98ystWFiitAR
…he Sentry DB SLO (review follow-up)

- "Nothing in this repository computes an answer-latency percentile" was false:
  scripts/eval-retrieval.ts enforces a p90 latency budget (p90BudgetMs, default
  20 s) using the percentile helper in scripts/eval-utils.ts, and the same
  budget is cited under "Anchors" in the section immediately below. The claim
  now says no *production monitor* computes one, and names the eval harness as
  the on-demand, fixture-corpus exception.
- The "Database latency - Sentry production DB span SLO" section was the only
  objective left without an alerted/not-alerted label, so a reader could not
  tell whether it is monitored. It is now labelled as a Sentry-side alert whose
  provisioning is unverified: SENTRY_AUTH_TOKEN appears in this repository only
  in the build-time sourcemap upload (next.config.ts), and no script
  provisions, reads, or tests the alert.

No threshold, SLO value, or alert code is changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHSyfuC6mS98ystWFiitAR
…eached production (review follow-up)

The PRIV-PROVIDER-RETENTION-SCHEDULE-PARITY externalEvidenceReference still
described 20260901033250 as staging-only. The migration ships in
supabase/migrations and its own header is environment-neutral ("environments
where the historical retention migrations ran before pg_cron was available"),
so merging it to main applied it to the live Clinical KB Database project
within seconds. One dated superseding sentence is appended; the dated, signed
attestation documents are left untouched.

check:privacy-readiness -> PRIVACY_READINESS_PASS mode=structural requirements=11
tests/privacy-readiness-contract.test.ts -> 8 passed

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHSyfuC6mS98ystWFiitAR
…(review follow-up)

The README told the reader to re-verify all four numbers "against the WA Health
service pages recorded as each contact's sourceUrl". Only three of the four are
WA Health: SYN-CRISIS-CONTACT-002/003/004 cite East Metropolitan Health Service
pages on emhs.health.wa.gov.au (a single MHERL page serving both the Perth
metropolitan and Peel numbers, and a Rurallink page), while
SYN-CRISIS-CONTACT-001 (000) cites the Commonwealth Triple Zero service at
triplezero.gov.au.

No number and no verifiedOn date is changed.

Note: the same "(the WA Health service pages)" phrasing appears in the fixture
file's own header comment at src/components/care-plan/mockups/fixtures.ts;
left as-is to keep this commit inside its reviewed scope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHSyfuC6mS98ystWFiitAR
…cation note

The header comment told the next reader to re-verify each number against
"the WA Health service pages" recorded as its sourceUrl. Three of the four
contacts cite East Metropolitan Health Service pages; 000 cites
triplezero.gov.au, which is Commonwealth. Same correction the README
carried in d5a29b8. No number and no verifiedOn date changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHSyfuC6mS98ystWFiitAR
@BigSimmo
BigSimmo marked this pull request as ready for review September 4, 2026 18:31
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@BigSimmo
BigSimmo enabled auto-merge (squash) September 4, 2026 18:31
@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_32ec3404-ae4d-41c3-9b19-d0551ca48ed3)

…urce-page correction

Package 21 merged to main while this branch was open and replaced this header
with a pointer to docs/care-plan/crisis-lines-verification.md, which is now the
record of when these numbers were checked and when they are next due. This
branch had corrected the same header's claim that every sourceUrl is a WA Health
page: three cite East Metropolitan Health Service pages and 000 cites Triple
Zero (Commonwealth).

Resolved as a union with one deliberate omission: main's pointer is kept and the
source-page correction folded into it, but this branch's twelve-month cadence
sentence is NOT reinstated, because the new document states a six-monthly cadence
and two different re-check intervals in the same repository is worse than either
one. The document owns the cadence; that disagreement is the owner's to settle.

No number and no verifiedOn date changed. Verified: tsc clean; care-plan,
privacy-page and calculators suites 93 passed (93).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHSyfuC6mS98ystWFiitAR
@BigSimmo
BigSimmo merged commit 7e1deb8 into main Sep 4, 2026
29 checks passed
@BigSimmo
BigSimmo deleted the claude/audit-fix-p10 branch September 4, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants