Skip to content

Stop the caseload list read fetching patient columns it discards (#RZVMPD) - #2533

Merged
BigSimmo merged 3 commits into
claude/caring-contacts-rules-r7r2ihfrom
claude/caring-contacts-rules-r7r2ih-2
Sep 2, 2026
Merged

Stop the caseload list read fetching patient columns it discards (#RZVMPD)#2533
BigSimmo merged 3 commits into
claude/caring-contacts-rules-r7r2ihfrom
claude/caring-contacts-rules-r7r2ih-2

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Stop the caseload list read fetching patient columns it discards: listPlans gets its own PLAN_LIST_COLUMNS, dropping patient_name, patient_mobile_number and patient_identifiers (#RZVMPD).
  • Leave PLAN_COLUMNS unchanged for readPlanRecord and selectPlanForUpdate, whose callers genuinely need the patient detail.
  • Add two guards — a static scan of the new constant and its wiring, and a wire-level test recording every statement listPlans issues.
  • Correct the listPatientNames and schedule-view doc comments, which stated the old behaviour as fact.

What was happening

Every render of the Patients directory ran select ${PLAN_COLUMNS} from caring_contacts.plans with no WHERE, pulling every patient's name, mobile number and identifier list for the whole team into the application process — and toPlanRecord mapped none of them.

Nothing was released. PlanRecord excludes patientDetail structurally and that guarantee held throughout; the narrowing was in the mapping, which releases nothing but fetches everything. It is now in the query.

Why patient_name goes too

The issue named two columns; this drops three. Names have their own read with its own capability check and its own patientNameDirectory access-audit object type (Ruling 91), which exists so "who read patients' names, and when" is answerable. Fetching names inside a read audited as plan under-counted that trail.

Why a separate constant rather than a narrowed PLAN_COLUMNS

getEpisode and markRetentionCleared legitimately need those columns via readPlanRecord. Narrowing the shared constant in place would leave getEpisode projecting undefined for three patient fields — a worse defect than this one, and one no type would catch.

Why two guards

Neither is sufficient alone. A static scan cannot see a second hand-written list query added later that never names the constant — which is the shape the original defect had. A runtime test alone would not catch the constant being widened for a path no test exercises. The static scan also asserts the constant is actually used, so a correctly narrowed list that was never wired up cannot read as a fix.

Both were confirmed to fail against the pre-fix code and pass after it.

Verification

  • npm run test — full offline unit suite, Test Files 949 passed (949), Tests 12292 passed | 1 skipped (12293)
  • npm run lint — eslint at --max-warnings 0, exit 0
  • npm run typechecktsc --noEmit, exit 0
  • npm run format (committed)
  • npm run caring-contacts:db:test against a local disposable Postgres 16 — Tests 214 passed (214), up from 213 by the new wire-level guard
  • node scripts/run-vitest.mjs run tests/caring-contacts-domain-isolation.test.tsTests 12 passed (12)
  • Mutation check: reverting listPlans to PLAN_COLUMNS turns both new guards red

The database suite ran against a throwaway local cluster started from /usr/lib/postgresql/16/bin. It is not the live Supabase project, and assertNotClinicalKbProject() refuses that ref by construction. No provider-backed gate was run.

npm run verify:pr-local not run: this remote container has no confirmed Chromium/Playwright provisioning, and the offline gates above cover the changed scope. GitHub remains the authoritative merge gate.

UI verification not run: no UI, routing, styling, or browser behaviour changed.

Risk and rollout

  • Risk: Low, and in the safe direction — this removes data from a query rather than adding any. The only way it could break a caller is if something downstream read a patient column off a listPlans row; toPlanRecord is the sole mapper and reads none of them, which the shared contract suite and the database suite both exercise.
  • Rollback: Revert the single commit. No data, schema, or configuration is touched — a SELECT list has no schema footprint.
  • Provider or production effects: None. No migration, no RLS or view depends on the select list, and no index or generated column is affected.
  • RAG impact: none

Clinical Governance Preflight

Completed voluntarily. The classifier returns clinicalRisk: false for these paths, but this is a change to how patient mobile numbers and identifiers are handled for a suicide-prevention cohort, which is squarely inside the repository's own "PR risk detection" list. The mechanical false is a substring accident, not a judgement.

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

Notes

  • Second of four stacked PRs. Base is the #59JT7W branch, so this PR's diff is only its own change; merge in sequence.

🤖 Generated with Claude Code

https://claude.ai/code/session_014X6aJ6PgN26a8XiDm4FVHF


Generated by Claude Code


Note

Low Risk
This removes PHI from a hot-path SELECT with no API shape change (PlanRecord never exposed those fields); risk is low unless an undocumented consumer read patient columns off raw query rows.

Overview
Narrows the Postgres caseload query so listPlans no longer selects patient_name, patient_mobile_number, or patient_identifiers on every Patients-directory render. A new PLAN_LIST_COLUMNS constant drives that read; full PLAN_COLUMNS stays for single-plan paths like readPlanRecord / selectPlanForUpdate that still need patient detail for getEpisode and retention clearance.

Privacy and audit alignment: names remain available only through listPatientNames (separate capability and patientNameDirectory audit). Comments in listPatientNames and schedule-view are updated to reflect query-level narrowing, not just PlanRecord typing.

Regression guards: a static source scan asserts PLAN_LIST_COLUMNS excludes patient fields and is wired into listPlans; a Postgres integration test records every SQL statement from listPlans and fails if any names a patient column.

Reviewed by Cursor Bugbot for commit bdf3001. Configure here.

…cards

#RZVMPD. `listPlans` selected `PLAN_COLUMNS` verbatim, so every render of
the Patients directory pulled every patient's name, mobile number and
identifier list for the whole team into the process -- and `toPlanRecord`
discarded all three. Nothing was released (`PlanRecord` excludes
`patientDetail` structurally, and that guarantee held), but the data need
never have been fetched.

- New `PLAN_LIST_COLUMNS`, used by `listPlans` only. `PLAN_COLUMNS` is
  unchanged for `readPlanRecord` and `selectPlanForUpdate`, whose callers
  genuinely need the patient detail -- narrowing the shared constant in
  place would leave `getEpisode` projecting `undefined` for three fields,
  which no type checks.
- `patient_name` is dropped alongside the mobile number and identifiers.
  Names have their own read with its own capability check and its own
  `patientNameDirectory` access-audit object type (Ruling 91), so pulling
  names inside a read audited as `plan` under-counted the "who read
  patients' names" trail.

Two guards, because neither alone is sufficient:

- A static scan of `PLAN_LIST_COLUMNS` and its wiring, beside the
  existing `first_contact_reason` and `preferred_name` scans. It also
  asserts the constant is actually used, so a correctly narrowed list
  that was never wired up cannot read as a fix.
- A wire-level test recording every statement `listPlans` issues, which
  a scan cannot replace: a second hand-written list query added later
  would never name the constant at all.

Both were confirmed to fail against the pre-fix code and pass after it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014X6aJ6PgN26a8XiDm4FVHF
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: bd29017b-a241-4580-927f-25ce6c6b44be

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@supabase

supabase Bot commented Sep 2, 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 ↗︎.

@BigSimmo
BigSimmo marked this pull request as ready for review September 2, 2026 05:57
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T06:03:25.672573Z e73e868 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cursor

cursor Bot commented Sep 2, 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_7e3904d2-ec07-4820-b848-87e0f6f6ba59)

@cursor

cursor Bot commented Sep 2, 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_9eeeb85a-128f-4921-8c12-29133cbea41c)

…2ih' into claude/caring-contacts-rules-r7r2ih-2
@cursor

cursor Bot commented Sep 2, 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_fa9efb5a-e1ea-4bf0-b308-d5dedc9f7b10)

@BigSimmo
BigSimmo merged commit 4551188 into claude/caring-contacts-rules-r7r2ih Sep 2, 2026
5 checks passed
@BigSimmo
BigSimmo deleted the claude/caring-contacts-rules-r7r2ih-2 branch September 2, 2026 09:16
BigSimmo pushed a commit that referenced this pull request Sep 2, 2026
PRs #2533 and #2535 were squash-merged into their base branches rather than
into main, which left this branch behind its own base. Its diff had started to
show unrelated main work as deletions -- the Phase 3 plan document, a ledger
inbox record, the ward-flow roadmap and the docs-link checker -- and the base
had stopped merging cleanly. Bringing the base forward fixes both.

The only conflict was at end of file: the base's squashed caseload-read test
ends the file, and this branch appends the mid-read clearance race test that
Codex review asked for. Kept the test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014X6aJ6PgN26a8XiDm4FVHF
BigSimmo pushed a commit that referenced this pull request Sep 2, 2026
… not run

Codex review finding (P2) on PR #2572, and it was right. All four review
records named individual hosted CI jobs as green and then ended with "No
provider-backed gate run" -- but AGENTS.md classifies hosted CI as
provider-backed, so the record contradicted itself. A later reader could not
tell whether those results were observed or inherited from someone else's
report, which is exactly what a review record exists to settle.

Both halves were true and the sentence conflated them. The hosted results WERE
observed: this session read them from the GitHub check runs via the MCP GitHub
tools, under a standing instruction to babysit these PRs. What was not run is
the set of gates that call OpenAI or Supabase.

Each record now separates LOCAL OFFLINE GATES from HOSTED CI, states who
observed the hosted result, and names the specific provider-backed gates that
were not run rather than denying provider contact wholesale.

Two records gained a correction beyond the wording: #2533 and #2535 had NO
hosted CI of their own, because repo CI is scoped to branches [main,
release/**] and their base was another feature branch. Their records now say
that plainly and point at the main-based head whose CI actually covered them,
instead of implying a pipeline ran on them.

Record filenames are a sha256 of the row, so these were regenerated through
ledger:append rather than edited in place; the four superseded files were
never merged, so no immutable history is rewritten and the net diff against
main is unchanged at four added records.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014X6aJ6PgN26a8XiDm4FVHF
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