v1.38.13 — a guardian sees the controls the routes accept - #942
Merged
Conversation
Add the one table of which record sections have a route declaring
requireRecordAuth("write") or ("manage"), derived from the route files,
and a structural guard that re-derives it from src/app/api on every run
and fails on a disagreement in either direction. The matcher tolerates
the line break a formatter puts after the opening parenthesis and
asserts a non-zero match count.
delegatedDomains() turns a grant's level and scope into the sections it
can add to and the sections it can change, keeping write within manage.
… payload Every accountAccess entry on GET /api/auth/me carries writableDomains and manageableDomains: the sections the grant opens, intersected with the sections whose routes accept a delegated write at that level. A READ grant gets two empty lists, a WRITE grant gets writable sections only, a MANAGE grant gets both, and the vault appears in neither. The client parser checks the structural invariants (no repeats, within the grant's sections, manageable within writable, nothing below the level) and requires the two published views of the active record to agree on the lists. Regenerate the OpenAPI document and extend the payload consumer guard with the active-record readers.
useRecordCapabilities answers per section now. canWriteDomain and canManageDomain read the two lists the account payload publishes; in one's own record they are always true, and under a refused, pending or unproven context always false. canManage becomes the coarse switch, true when any section is manageable, and is what the banner and the navigation read. Every add, edit and delete control names its own section: labs, mind, profile, illness, cycle, medications, measurements, documents. Creates that a WRITE route answers (visits, practitioners) ask canWriteDomain. The generic delete button, the selection bar, the capture picker, the dashboard quick entry and the bottom nav take the section from their callers. Controls whose routes resolve the caller rather than the record (settings shortcuts, chart overlay preferences, custom metrics, the Today rail, OCR, efficacy targets, credential tabs) ask for the caller's own record instead, and the customise-shortcut guard now requires that shape. The vault has no delegated write route, so its controls stay withheld under every grant, and the documents page says so in one sentence where the upload control would be, in all seven locales.
…and the browser The integration leg reads GET /api/auth/me as a guardian acting on a managed profile and asserts both lists, lands a mood entry and an allergy on the profile's record, is refused a vault write, and shows a WRITE delegate writable sections without manageable ones together with the refused mood entry the list withheld. The browser leg opens the managed profile, adds a mood entry through the control that used to be hidden and asserts the POST's status, and finds the owner-only sentence and no upload control on the documents page. The spec gets its own session jar and runs in the desktop project only, like every other spec that moves the record selector.
…contract The account-access integration contract compares whole entries, so the two lists the payload now publishes belong in its expected shapes. The managed-record leg posts the mood entry with the logged-at instant the create schema requires.
…cept Version anchors, the OpenAPI document and the changelog entry. Reported in #939.
…pabilities # Conflicts: # public/sw.js
pnpm-workspace.yaml carried two overrides for the same packages: an older floor (hono < 4.12.25, js-yaml >= 4.0.0 < 4.3.1) and the newer one added for the scanner findings. The older selector won the resolution, so the lockfile kept hono 4.13.3 and js-yaml 4.3.1 and four of the seventeen findings stayed open after v1.38.12. The stale bounds are gone; the lockfile now resolves hono 4.13.7 and js-yaml 4.3.2.
The release commit carried the version anchors and the OpenAPI document but not the changelog entry; it is added here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #939. A person acting on a managed profile, or on a record shared with them at the manage level, saw no add, edit or delete controls on Mood, Mental wellbeing, Visits, Profile, Labs and others. The server accepted those writes; the client hard-wired
canManageto false for every shared record, a hold-back from v1.37.0 "until the routes answer" that never got lifted. Lifting it globally would have painted controls that 403 in the documents surfaces, so the fix is per domain and server-authoritative.One source of truth.
src/lib/sharing/domain-write-support.tsrecords, per share domain, whether any route accepts delegated writes atwriteand atmanage. A structural test scans everysrc/app/api/**/route.tsforrequireRecordAuth("write"|"manage", domain)and fails on drift in either direction. Derived table: measurements, medications, labs, profile and illness accept write and manage; mind and cycle accept manage only; documents accept neither.Server. Every
accountAccessentry onGET /api/auth/megainswritableDomainsandmanageableDomains: the sections the grant opens, intersected with the table at the grant's level. Additive; OpenAPI regenerated; the account-payload consumer guard covers the two fields.Client.
useRecordCapabilitiesgainscanWriteDomainandcanManageDomain;canManagenow means "at least one manageable domain" and only the shared-record banner and the navigation still use that coarse form. Every former consumer names its domain. Owner-only by decision, with the reason at each site: the layout wrenches, chart overlay controls, custom metrics, the today hero, the efficacy tab, the labs OCR probe, and the medication detail tabs that mix caller routes. The Documents page shows a guardian a sentence that only the record owner can add documents, in all seven locales, instead of an empty slot.Tests. Hook and affordance unit tests replace the hold-back pin with the new contract; the structural guard above; an integration test where a guardian reads the two lists on
/api/auth/me, creates a mood entry and an allergy on the managed profile, and is refused a document write; an e2e spec where the guardian adds a mood entry and sees the owner-only sentence on Documents.Gates on the final commit: typecheck, lint (three baseline warnings), format, openapi:check, unit suite 22 893 passed, full integration suite 215 files / 1930 tests, production build, e2e spec locally.
Issues 2 to 4 of #939 (module switches per managed profile, editing a managed profile, sex at creation) are the management surface and follow separately; the issue stays open.