Skip to content

feat(on-call): publish On Call reference entries to every visitor, keeping personal numbers owner-only - #2633

Merged
BigSimmo merged 8 commits into
mainfrom
claude/on-call-mode-health-n17dvm
Sep 5, 2026
Merged

feat(on-call): publish On Call reference entries to every visitor, keeping personal numbers owner-only#2633
BigSimmo merged 8 commits into
mainfrom
claude/on-call-mode-health-n17dvm

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Summary

This PR changes who can read On Call content, from the signed-in owner alone to anyone who opens the page, signed in or not. That is a deliberate owner decision (2026-09-05), not a side effect — the earlier PR title described this branch as a documentation change, which understated it.

  • On Call reference entries (is_personal = false) are now returned to every visitor. Anonymous visitors previously received an empty list and an "unauthorized" search state.
  • Entries marked is_personal = true — personal numbers and anything the owner flags as private — remain owner-only and are never returned to an anonymous visitor.
  • A signed-in owner sees the shared set merged with their own entries, de-duplicated by id and ordered by sort_order.
  • The "unauthorized" On Call search status is removed, because signed-out is no longer a failure state for this mode.
  • The design spec is amended in place with a dated record of the decision and the is_personal exception.
  • Design-system docs that still said "fifteen modes" now say "seventeen".
  • Two append-only ledger request files ride along under docs/outstanding-issues-inbox/, recording the home-page LCP budget finding and the linked_document_ids foreign-key gap. They are inert data awaiting a separate reconciliation run and change no behaviour.

No clinical content is added, changed, or authored by the app anywhere in this diff, fixtures included. The change is a read-scope change only; nothing about how entries are written, reviewed, or dated moves.

Verification

  • npm run test:focused -- --files src/lib/on-call/repository.ts src/lib/on-call/search.ts src/lib/on-call/entry-store.ts src/app/api/on-call/entries/route.ts
  • npm run typecheck
  • npm run lint
  • Pre-push guard (guard-push.mjs) re-run against the pushed commit 7e090d07f: exit 0, which includes its isolated-worktree Prettier check.
  • UI verification not run: no visual or layout change; the only component edit removes a status branch that can no longer occur. Chromium journeys left to CI.

Risk and rollout

  • Risk: high — this is the highest-consequence change on the branch. Anything an editor stores as a non-personal On Call entry becomes world-readable the moment this merges. There is no login wall and no colleagues-only tier — those were considered and explicitly declined by the owner in favour of public access.
  • The blast radius is bounded to the on_call_entries table. No other table, route, or document surface changes scope.
  • Rollback: revert the three behaviour commits on this branch (525385ee4, f95c174b7, bea46f611); the later commits are main merges and the two ledger notes. Reverting restores owner-only reads immediately; no data migration is involved and no schema changes ship here.
  • Before merge, confirm no entry currently stored with is_personal = false contains a number or note intended to stay private.

Clinical Governance Preflight

  • 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 against those boxes: the route continues to run through the server-only admin client, so no service-role credential reaches the browser. The public read is confined to on_call_entries rows the owner has already marked non-personal; document access, citations, and source-staleness behaviour are untouched. No decision-support behaviour changed, so the classification position is unmoved.

RAG impact: no retrieval behaviour change — this diff does not touch retrieval, ranking, selection, or the eval harness.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC


Generated by Claude Code


Note

High Risk
Any non-personal On Call entry becomes world-readable on merge with no login wall; blast radius is limited to on_call_entries reads, with is_personal as the only privacy gate.

Overview
On Call read access is widened from signed-in owners only to any site visitor. Non-personal rows (is_personal = false) are returned through new repository helpers (fetchSharedOnCallEntries, fetchVisibleOnCallEntries); signed-in owners still get their own rows merged in, including personal numbers the shared query never exposes.

The GET /api/on-call/entries route no longer short-circuits anonymous callers to an empty list—it loads the visible set after rate limiting and keeps signedOut only to gate editing in the client. Search and offline caching follow: unauthorized is dropped from On Call search status, signed-out users see real counts, and the entry store caches signed-out payloads when they carry shared data but still refuses to overwrite a good cache with an empty response.

Docs record the 2026-09-04 owner decision (amended On Call spec) and bump design-system mode counts fifteen → seventeen. Contract and unit/DOM tests pin personal exclusion, API shape, and the new UX.

Reviewed by Cursor Bugbot for commit 6ef9db3. Configure here.

… fifteen

SPEC §3 and DECISIONS §C5 both still said "fifteen modes". The registry in
src/lib/app-modes.ts declares seventeen: sources landed in #2510 and on-call
in #2608. The 2026-09-02 repository audit confirmed both strings (L81/L94) and
deliberately left them for the design-system owner rather than correcting them
in a documentation-only commit.

The claim these two lines make is unchanged and still holds — three
semantically-named identity families cover every mode, with no fourth family —
so only the count moves. No inbound link targets the C5 heading, and no test or
script pins either phrase.

Historical records keep their original numbers: the audit findings and the
superseded plans under docs/superpowers/ describe what was true when they were
written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
@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: aa04f24e-faaf-4e22-a5f7-4dccef30f56e


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.

@BigSimmo
BigSimmo marked this pull request as ready for review September 4, 2026 18:29
@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.

@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_d11a64e9-789d-4e63-984d-d1431c263a51)

…l numbers

Owner decision, 2026-09-04, taken with the consequence stated: this app has no
login wall, so "public" means readable by anyone who reaches psychiatry.tools,
not "readable by signed-in colleagues". There is no cohort tier to choose
instead — that remains the deferred project in the spec's §12. The owner
confirmed on that basis.

Reads are no longer owner-scoped. fetchSharedOnCallEntries returns entries to
any caller; fetchVisibleOnCallEntries adds the viewer's own on top, as two .eq()
reads merged rather than one interpolated PostgREST or= filter, which is the
injection edge withOwnerReadScope guards with a UUID pattern.

Writes are unchanged: creating, editing and verifying still require an account
and still stamp owner_id from the session, never from the request body.

One thing is never published. An entry flagged is_personal stays with the
account that wrote it. The editor labels that checkbox "Personal number —
excluded from the printable card and any export", and a world-readable fetch is
an export. tests/on-call-repository.test.ts pins it, and the assertion was
mutation-tested: removing the filter kills three tests.

Consequent client changes, each a behaviour reversal rather than a test fix:

- Signed out is no longer a fault. onCallSearchStatus drops its unauthorized
  branch (and that member of its union, which stays in the band's wider one for
  modes that do gate on a session), so a signed-out search reports a truthful
  count instead of "Sign in to search".
- The cache guard is restated, not removed. It skipped signed-out responses
  because they were always empty; they no longer are, so skipping them threw
  away good data. What it actually protected — an empty response erasing a
  non-empty cache mid-shift — is now the condition, which also covers the server
  returning nothing for any other reason.

Both privacy gates still pass untouched: check:owner-scope and the
retrieval-owner-filter guard, because the scoping lives in the repository rather
than the route, so nothing needed an exemption.

Spec amended in place with a dated note; the audience and storage rows no longer
described the shipped behaviour.

Full offline suite: 16,810 passed, 0 failed.

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

The push guard's source typecheck rejected reading `call[0]` off an untyped
vi.fn(): it records the call but types its arguments as an empty tuple, so the
new assertions that read back which column each `.eq()` filtered on had no
element to index. `npm run typecheck` uses a different project file and did not
catch it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
Reading `call[0]` off the fake chain needed the mock's arguments typed, and
every way of doing that was worse than the assertion it enabled: naming the
parameters tripped no-unused-vars, and typing them through vi.fn generics made
`chain` circular in its own initializer.

`expect(eq).not.toHaveBeenCalledWith("owner_id", expect.anything())` says the
same thing — the shared read never narrows by owner — with no typing at all.
Re-mutation-tested after the rewrite: removing the personal-entry filter still
kills three tests.

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

cursor Bot commented Sep 5, 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_7b6b5de5-b65b-4bf0-adcd-a49f0c57e3b1)

@BigSimmo
BigSimmo enabled auto-merge September 5, 2026 08:35
@BigSimmo BigSimmo changed the title docs(design-system): the identity families cover seventeen modes, not fifteen feat(on-call): publish On Call reference entries to every visitor, keeping personal numbers owner-only Sep 5, 2026
@cursor

cursor Bot commented Sep 5, 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_6f030f5b-546b-411c-9f59-ac604b4bfef2)

BigSimmo and others added 3 commits September 5, 2026 17:01
…ings

Two loose ends surfaced while building On Call and were not recorded anywhere
outside the session transcript.

The first is the home-page LCP sitting on the Lighthouse trip-wire. The hero
heading paints ~750-880ms in because the search-app loading.tsx Suspense
boundary makes React stream the real hero into a hidden div and reveal it a
frame later. An A/B on an identical browser build (881.3ms vs 881.5ms) refuted
an earlier attribution to another PR, so this is architectural rather than a
regression. The recommendation is to rebaseline the budget, because removing
the boundary changes how the first screen appears to users.

The second is that on_call_entries.linked_document_ids is a uuid[], so Postgres
cannot enforce a foreign key to documents. It degrades safely — an unresolvable
id renders as a missing link, never a wrong one — and the recommendation is to
leave it rather than restructure the link into a join table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
@BigSimmo
BigSimmo merged commit 77a6488 into main Sep 5, 2026
32 checks passed
@BigSimmo
BigSimmo deleted the claude/on-call-mode-health-n17dvm branch September 5, 2026 09:41
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