From 2980cca20048ae48f4f17512fea173c14944e12a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 7 Aug 2026 08:20:08 +0200 Subject: [PATCH] docs: bring the sharing copy up to what the release ships The operator page still said every grant is read-only, one release after the write level shipped, and the table of what a grant cannot reach predates both the manage level and managed profiles. Rewritten against the frozen behaviour: the eight sections a grant can be narrowed to and what each opens, the three levels and what each adds, the step-up that is unconditional on a managed profile and conditional on an ordinary invitation, and what a guardian may keep for a record that has no login of its own. The AI row is corrected rather than dropped: a manage grant reads insights that already exist, and no level causes an outbound provider call. Every claim on that page was read out of the code before it was written down. The one about backups was cut instead, because it was not. --- CHANGELOG.md | 80 +++++++++++++ docs/README.md | 9 +- docs/self-hosting/account-sharing.md | 162 ++++++++++++++++++++++++--- 3 files changed, 234 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d63810152..0ffcbbb32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,79 @@ ## [Unreleased] +## [1.37.0] — 2026-08-07 + ### Added +- Sharing a record no longer has to mean sharing the whole of it. An invitation + can name the parts it opens: readings, medications, lab results, health + background, illness, mood and mind, cycle, documents. Everything you do not + pick stays closed, and a section you kept back is refused the same way a + record nobody shared with you is, so what you held back is not visible from + the outside. + + Two things the invitation screen says out loud rather than leaving you to + work them out. A section can mention the rest of your health in its notes and + its names, so sharing a section shares whatever was written in it. And the + dashboard overview, the health score and the daily digest appear only when + the entire record is shared, because a figure computed from part of a record + would read as a figure about the person. + + Every grant written before this release opens the entire record and is + untouched. Nobody has to agree to anything again. + +- A third level of access. Read access looks at the record. Write access adds + to it. Manage access can also change and remove what is already there, + including entries you made yourself, and can read the insights generated from + the record. The line it stops at is the account around the record: your + login, your second factor, your connected services, your API tokens, where + your notifications go, which modules and thresholds are on, and who else has + access. + + Offering manage access asks for your second factor, so it can only be done in + a browser. Accepting it can be done anywhere. It always covers the entire + record. There is no way to hand somebody everything about part of you, because + a note in one section can be about any other. + + Everything a manager does is recorded under their own name, with a verb that + says what it was rather than "made a change". + +- A health record for somebody who does not sign in. @balajiv113 asked in #360 + for a way to look after a family member, and a grant between two accounts only + ever covered half of that. The other half is the person who has no account to + begin with: a child, or somebody you care for. + + A managed profile has no login and no e-mail address. You give it a name, + optionally a real date of birth, and the language and timezone its own days + and reminders are measured in. Nothing is invented from a year you did not + give. Creating one asks for your second factor, and you become its first + guardian in the same transaction, so there is no moment where the record + exists and nobody is looking after it. + + You can invite a second guardian, who accepts the invitation the way any + other is accepted. The record can never be left with nobody: the last + guardian cannot hand it back and cannot be removed, and the screen says so + where the refusal happens. The way out is to add somebody else, or to delete + the profile. + +- The reminders a managed profile produces reach the people looking after it. A + record with no login has no phone and no chat of its own, so medication + reminders, measurement reminders, safety-floor alerts and low-stock alerts go + to its guardians, each in their own language and over the channels they had + already chosen. The message names whose record it is about. It carries no + buttons: opening the app takes you through the ordinary switch into that + record rather than acting on it from a lock screen. + + One guardian turning off their own reminders on their own phone does not + silence the others. + +- A guardian can keep a managed profile's settings. Its modules, its units, its + language and timezone, its thresholds and its notification preferences, read + and written as that record's settings rather than as yours. Your own screens + stay in your own language while you do it. Connected services show a status + and nothing else for now: connecting, syncing and disconnecting a wearable on + somebody else's behalf is not part of this release. + - The document vault opens inside a shared record. `/documents` was offered in the navigation of somebody else's record and then refused every read behind it, so the page rendered an error card and the illness view rendered another @@ -33,6 +104,15 @@ ### Fixed +- Two tabs of one browser could disagree about which record they were in. + Switching records moves a session rather than a tab, so a tab left open on + the record you just left went on reading and writing there until somebody + reloaded it. Every request now carries the record the tab believes it is in, + and one naming the record it has left is refused with an answer the tab + recovers from by re-reading who it is. A session that has never opened + somebody else's record is unaffected, and so is the phone app, which carries + the record on the request itself and never held a selector to go stale. + - Somebody with write access could still overturn a dose you had already recorded. The rule that keeps that with the owner was on one request and the medication card reaches the same entry through another, where an explicit diff --git a/docs/README.md b/docs/README.md index 084dd655c..5384a5694 100644 --- a/docs/README.md +++ b/docs/README.md @@ -28,10 +28,11 @@ notes, and machine-readable specs that ship alongside the source: own offline GeoLite2 databases via `GEOLITE2_DIR` instead of sending login IPs to a third-party lookup. And [`self-hosting/account-sharing.md`](./self-hosting/account-sharing.md) — - letting one account on the instance open another's health record: what - the person given access can and cannot reach, what revocation does and - does not undo, and which trust properties of the instance are unchanged - by it. + letting one account on the instance open another's health record: the + three access levels, the eight sections a grant can be narrowed to, + managed profiles for somebody with no login and the guardians who look + after them, what revocation does and does not undo, and which trust + properties of the instance are unchanged by any of it. - [`migration/`](./migration/) — release-by-release migration notes. Read the entry for the version you're upgrading from. - [`audit/`](./audit/) — per-release audit summaries archived for diff --git a/docs/self-hosting/account-sharing.md b/docs/self-hosting/account-sharing.md index 4b5190c31..abd6a128a 100644 --- a/docs/self-hosting/account-sharing.md +++ b/docs/self-hosting/account-sharing.md @@ -5,8 +5,12 @@ can open the other's health record. A parent and an adult child, two partners, somebody keeping an eye on a relative's blood pressure — the case this exists for is a household, not an organisation. -This page describes what that connection does, what it deliberately does -not do, and which of the instance's existing trust properties it leaves +Since v1.37.0 there is a second arrangement beside it: a **managed +profile**, a health record for somebody who has no account at all. That +half is described further down, under "Records with no login". + +This page describes what these arrangements do, what they deliberately do +not do, and which of the instance's existing trust properties they leave exactly as they were. ## The shape of it @@ -25,7 +29,7 @@ the settings copy these are the owner and the person they invited. - Once accepted, the record shows up in that person's user menu under "Open a record". Opening it reloads the app into the owner's record. - A banner across the top of every page names whose record is open and - says it is read-only, with a button back to the person's own. It is + what the access admits, with a button back to the person's own. It is loud on purpose: somebody who forgets they are switched will log their own reading into another person's record, and there is no undo for an entry that was never theirs. @@ -36,11 +40,61 @@ the settings copy these are the owner and the person they invited. ## What the other person can and cannot do -**Read the health record.** Measurements, medications, mood, labs, the -illness journal, preventive-care reminders, documents. In this release -that is the whole of it: every grant is read-only. The write level exists -in the data model so that adding it later is enforcement work rather than -a migration, and no invitation can create one today. +An invitation carries two answers: how much of the record it opens, and +what may be done inside it. + +### How much of the record + +Either the entire record, which is what sharing has always meant and what +every grant written before v1.37.0 carries, or a set of named sections +chosen when the invitation is written: + +| Section | What it covers | +| ----------------- | -------------------------------------------------------------------------- | +| Readings | Weight, blood pressure, pulse, glucose, sleep, workouts and custom metrics | +| Medications | The medication list, its schedules, doses taken, side effects and stock | +| Lab results | Lab results and the analytes behind them | +| Health background | Allergies, family history and the facts in the health profile | +| Illness | Illness episodes and the day logs kept under them | +| Mood and mind | Mood entries and the mental-health questionnaire history | +| Cycle | Cycle tracking | +| Documents | Everything in the document vault | + +A section that was not shared is refused exactly the way a record nobody +shared is, so the shape of what was held back is not readable from +outside. Sections are route families and nothing finer: there is no row +filtering, no field filtering and no redaction inside a section that WAS +shared. The invitation screen says this in as many words, because a note +or a document name inside one section can be about any other. + +Aggregate surfaces (the dashboard overview, the health score, the daily +digest) appear only on a whole-record grant. A figure derived from part of +a record reads as a figure about the person, and it would not be one. + +A stored scope never grows. A section added by a later release is not in +a set somebody ticked before it existed. + +### What may be done inside it + +Three levels, fixed when the invitation is written. Raising one means +revoking and inviting again, because widening a grant in place would carry +a consent the other person never gave. + +| Level | Adds | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Read | Read what the grant opens, and change nothing. | +| Write | Add entries: a reading, a lab result, an analyte, an illness entry, a side effect, a medication, and marking a dose taken or skipped. Editing, deleting and restoring stay with you. | +| Manage | Change and remove entries, including ones the owner wrote; record the health background; read the insights generated from the record. Always the entire record. | + +Offering **manage** asks the owner for a fresh second factor if they have +one enrolled, which makes it a browser-only act either way: a Bearer +caller is refused before anything else happens and told to use a browser. +Accepting it has no such restriction. Manage never carries a section +scope, because "they can do anything, but only to part of you" promises a +boundary an edit cannot keep. + +Everything a manager does is written to the trail under their own name, +with a verb naming the act rather than a generic "made a change". **Nothing about the account around the record.** This is the line the whole feature is built on, and it is drawn on the server: @@ -53,10 +107,16 @@ whole feature is built on, and it is drawn on the server: | Connected services (Withings, Fitbit, WHOOP, Google Health, Polar, Oura, Strava, Nightscout) | Re-pointing a sync, or feeding somebody else's wearable into this record | | Notification channels and push devices | The owner's health alerts redirected to another phone or chat | | Exports, the health-record archive, clinician share links | A one-click copy of everything, or a new door that outlives the access | -| Insights and the Coach | AI processing of the owner's data, under the owner's own consent | +| Asking an AI provider anything | AI processing of the owner's data, under the owner's own consent | +| Modules, thresholds, units, the record's language and timezone | Configuration of the account, not content of the record | | Sharing itself | Inviting somebody else, widening the access, or passing it on | | Locale, theme, dashboard layout | Presentation belongs to the person, not to the record | +This table is about a grant between two accounts. A managed profile is +the one place where some of it is deliberately different, because a record +with no login has nobody else to keep its own configuration: see "Records +with no login" below for exactly which part of it a guardian may reach. + The last two rows are worth a sentence each. Sharing is not transferable: somebody given access cannot invite a third person, cannot raise their own level, and cannot hand it on. And display preferences @@ -66,8 +126,11 @@ does not change your own theme, language or dashboard. The AI row is the one that looks arbitrary and is not. Server-managed AI features send the record's data to a provider under a consent the record's OWNER gave, for their own use. Somebody else triggering that would create -a consent-shaped act the owner never made, so those surfaces stay -owner-only. +a consent-shaped act the owner never made. So a manage grant can READ +insights that were already generated, and nothing a delegate does at any +level causes an outbound provider call or queues a generation job. The +owner's own path is unchanged and is the control that proves the +difference. These are not checks that a page forgot to render. Every route in the product refuses by default while a browser is acting on another account; @@ -94,6 +157,62 @@ dates and with who ended it, because "who had access, from when to when, and who ended it" is a question a deleted row cannot answer. Re-inviting the same person creates a new row beside the old one. +## Records with no login + +A **managed profile** is a health record for somebody who does not sign +in: a child, or an adult who is cared for. It is a real account row with +no password, no passkey and no e-mail address, and it is never presented +as something to log in as. + +- It is created from **Settings → Shared access**, under a fresh second + factor. An account with no second factor enrolled is refused rather than + waved through, because the act creates a permanent management + relationship over a person who cannot object to it. +- Creation takes a display name, optionally a real date of birth, and the + language and timezone the record's own days and reminders are measured + in. A year on its own is never turned into the first of January. The + language belongs to the record, not to the guardian reading it, so the + guardian's own screens stay in their own language. +- The creator becomes the first **guardian** in the same transaction, over + an ordinary accepted MANAGE grant with the profile as grantor. If either + half fails, neither happens: there is no window in which the profile + exists with nobody looking after it. +- A second guardian is invited and accepts exactly like any other + invitation. An accepted guardian grant does not expire; the invitation + token still can. +- **A managed profile can never be left guardian-less.** Revoking, + handing back, deleting a guardian's own account and deleting the + profile all take the same lock on the profile and re-check the count + inside it, so two people cannot both leave at once. The last guardian is + refused, with a message naming the two ways out: add another guardian, + or delete the profile under a fresh second factor. Deleting the profile + deletes everything recorded for it. +- A guardian may keep that record's settings: modules, units, thresholds, + its language and timezone, and its notification preferences. Connected + services are visible as a status only. Connect, callback, sync, + credential, disconnect, resume and test are not guardian surfaces in + this release, and their deep links land on the shared-record unavailable + page rather than on a raw refusal. +- **Its reminders go to its guardians.** A record with no login has no + channels of its own, so medication reminders, measurement reminders, + safety-floor alerts and low-stock alerts fan out to every active + guardian, in that guardian's own language and over the channels that + guardian already configured. Nothing else fans out; every other event + stays on the path it was already on. The payloads carry no take or skip + action, on any channel, so a notification about somebody else's record + cannot be acted on from a lock screen. One guardian suppressing + server-side reminders on their own device does not suppress anybody + else's. +- Delivery attempts record both the record the message is about and the + person it was sent to, so the admin notification diagnostic can answer + "who was this for" and "whose record was it about" separately. + +There is no emancipation flow: no route, no control and no copy suggests +a managed profile can be turned into an ordinary account. The data model +leaves room for it (the credentials are nullable and the managed marker +can be cleared) and clearing that marker stops the notification fan-out, +but nothing in v1.37.0 exercises any of that. + ## Seeing what happened **Settings → Shared access** answers three questions for the owner, at @@ -142,8 +261,9 @@ re-reads the trust model: Nothing to configure. Sharing is available on every instance, needs no environment variable, and does nothing until one account invites another. +The same is true of managed profiles. -Two operational notes: +Four operational notes: - **Invitations disclose whether a username exists.** An invitation to a name nobody uses is refused with "no account with that name". That is @@ -155,4 +275,20 @@ Two operational notes: rows naming that account as the one who opened somebody's record stay, and render as "a deleted account" — nulling them would turn "your daughter opened your record" into "you opened your record", which is - worse than an unresolvable id. + worse than an unresolvable id. Deleting the last guardian's account is + refused while the managed profile would survive it, so an account + deletion cannot strand a record. +- **A managed profile is a user row and appears as one.** It is listed in + the admin user list like any other account, with no e-mail address + beside it and an internal username nobody is ever asked to type. No + login path will accept it: there is no password hash to verify against + and no address to look it up by. +- **The second-factor requirement is not the same on both features, and + the difference is deliberate.** Creating a managed profile, deleting + one and inviting a second guardian require a second factor + unconditionally: an account with none enrolled is refused and told to + set one up, because those acts create or end a permanent relationship + over somebody who cannot object. Offering manage access on an ordinary + invitation asks for a fresh factor only from an account that has one, so + an instance where nobody has enrolled a factor can still use every level + of ordinary sharing. All of them are browser-only either way.