feat(on-call): publish On Call reference entries to every visitor, keeping personal numbers owner-only - #2633
Conversation
… 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
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: 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. Comment |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Bugbot couldn't run - usage limit reachedBugbot 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
Bugbot couldn't run - usage limit reachedBugbot 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) |
Bugbot couldn't run - usage limit reachedBugbot 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) |
…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
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.
is_personal = false) are now returned to every visitor. Anonymous visitors previously received an empty list and an "unauthorized" search state.is_personal = true— personal numbers and anything the owner flags as private — remain owner-only and are never returned to an anonymous visitor.sort_order."unauthorized"On Call search status is removed, because signed-out is no longer a failure state for this mode.is_personalexception.docs/outstanding-issues-inbox/, recording the home-page LCP budget finding and thelinked_document_idsforeign-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.tsnpm run typechecknpm run lintguard-push.mjs) re-run against the pushed commit7e090d07f: exit 0, which includes its isolated-worktree Prettier check.Risk and rollout
on_call_entriestable. No other table, route, or document surface changes scope.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.is_personal = falsecontains a number or note intended to stay private.Clinical Governance Preflight
Clinical KB Database(sjrfecxgysukkwxsowpy)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_entriesrows 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_entriesreads, withis_personalas 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/entriesroute no longer short-circuits anonymous callers to an empty list—it loads the visible set after rate limiting and keepssignedOutonly to gate editing in the client. Search and offline caching follow:unauthorizedis 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.