mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
Merge remote-tracking branch 'origin/main' into feat/onboarding-wizard
# Conflicts: # docs/api/openapi.yaml # src/lib/openapi/error-codes.ts
This commit is contained in:
commit
55ff6df205
87 files changed
+2080
-399
No files matched your search
+77
-13
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: HealthLog API
|
||||
version: 1.38.15
|
||||
version: 1.38.16
|
||||
description: >-
|
||||
Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest.
|
||||
|
||||
@@ -581,7 +581,11 @@ paths:
|
||||
temperatures), plus the single headline finding (resting-heart-rate-by-phase, falling back to HRV). The same
|
||||
Welch t-test + Benjamini-Hochberg machinery the mood-factor crosstab runs; only rows with p < 0.05 AND q ≤ 0.10
|
||||
surface. Strictly gender-gated — phase never appears on the general `/api/insights/correlations` route.
|
||||
Observational only, never causal. Gated: `cycle.disabled` 403 when the feature is off."
|
||||
Observational only, never causal. Gated: `cycle.disabled` 403 when the feature is off. Declared for the `cycle`
|
||||
section; the outcome columns are fenced per section at the query, so a grant scoped to the cycle alone receives
|
||||
the phase rows with no `lutealAvg` / `follicularAvg` / `delta` for measurement or mood outcomes and no lagged
|
||||
discovery, exactly as a record with nothing logged would read. The owner and a whole-record grant are
|
||||
unchanged."
|
||||
responses:
|
||||
"200":
|
||||
description: Phase-correlation rows + headline.
|
||||
@@ -1789,6 +1793,20 @@ paths:
|
||||
type: string
|
||||
format: binary
|
||||
"401": *a1
|
||||
"403":
|
||||
description: >-
|
||||
The Bearer token carries a narrow scope. The encrypted archive needs a cookie session or a full-access
|
||||
token; the refusal happens before any second-factor proof is looked at.
|
||||
|
||||
|
||||
Refused: the request was made while the session is acting on another account (`meta.errorCode` =
|
||||
`sharing.not_permitted`). This operation resolves the CALLER and never a named record, so under a switch it
|
||||
refuses instead of quietly answering with the caller's own — leave the shared record first. No grant at any
|
||||
level opens it.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a4
|
||||
"429": *a5
|
||||
/api/auth/check-user:
|
||||
@@ -12237,8 +12255,15 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"403":
|
||||
description: The Bearer token carries a narrow scope. Erasure needs a cookie session or a full-access token; the refusal
|
||||
description: >-
|
||||
The Bearer token carries a narrow scope. Erasure needs a cookie session or a full-access token; the refusal
|
||||
happens before any second-factor proof is looked at.
|
||||
|
||||
|
||||
Refused: the request was made while the session is acting on another account (`meta.errorCode` =
|
||||
`sharing.not_permitted`). This operation resolves the CALLER and never a named record, so under a switch it
|
||||
refuses instead of quietly answering with the caller's own — leave the shared record first. No grant at any
|
||||
level opens it.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -12460,8 +12485,15 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"403":
|
||||
description: The Bearer token carries a narrow scope. Erasure needs a cookie session or a full-access token; the refusal
|
||||
description: >-
|
||||
The Bearer token carries a narrow scope. Erasure needs a cookie session or a full-access token; the refusal
|
||||
happens before any second-factor proof is looked at.
|
||||
|
||||
|
||||
Refused: the request was made while the session is acting on another account (`meta.errorCode` =
|
||||
`sharing.not_permitted`). This operation resolves the CALLER and never a named record, so under a switch it
|
||||
refuses instead of quietly answering with the caller's own — leave the shared record first. No grant at any
|
||||
level opens it.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -14201,12 +14233,19 @@ paths:
|
||||
tags:
|
||||
- Records
|
||||
summary: Plan a booster reminder from a dose
|
||||
description: "Mints (or re-anchors) the booster reminder a logged dose suggests. The minted row is an ordinary `origin:
|
||||
description: >-
|
||||
Mints (or re-anchors) the booster reminder a logged dose suggests. The minted row is an ordinary `origin:
|
||||
VORSORGE` measurement reminder carrying the dose's primary antigen as a server-side match key; it lists on the
|
||||
preventive-care surface and rings through the same engine as every other checkup. Re-confirming re-anchors the
|
||||
existing reminder instead of minting a second one: 201 carries a fresh mint (`minted: true`), 200 a re-anchor. A
|
||||
free-text-only dose has no antigen to remind on and is refused with 422. Audits as
|
||||
`vaccination.booster.planned`."
|
||||
`vaccination.booster.planned`.
|
||||
|
||||
|
||||
The operation is declared under the health background and the row it mints belongs to the measurements section,
|
||||
so it crosses a section boundary: a delegate whose grant does not also open `measurements` is refused with 403
|
||||
`vaccination.booster-out-of-scope` and nothing is written. The dose itself still logs — only the booster does
|
||||
not.
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
@@ -14233,7 +14272,25 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/MintVaccinationBoosterEnvelope"
|
||||
"401": *a1
|
||||
"403": *a9
|
||||
"403":
|
||||
description: >-
|
||||
Refused: the caller's grant does not open this record's measurements, and the booster reminder lives there
|
||||
(`meta.errorCode` = `vaccination.booster-out-of-scope`). Nothing was written.
|
||||
|
||||
|
||||
Refused: this request named a record the caller may not act on (`meta.errorCode` = `sharing.access.denied`).
|
||||
Byte-identical for an account that does not exist, an account that granted nothing, a grant whose sections
|
||||
do not reach this surface, and a read grant on a request that writes — the response carries no reason, so it
|
||||
is not an account-enumeration oracle. The reason reaches the record owner's activity feed and the operator's
|
||||
audit trail instead. On the cookie transport the same code answers one further case: a session that has
|
||||
entered a shared record and sends a request without its record-context assertion, which is a client older
|
||||
than the fence — leave the record and reload. A request that names no record but the caller's own never
|
||||
reaches this response, so a client that never switches and never sends the per-request account selector will
|
||||
not see it.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"404": *a28
|
||||
"422": *a4
|
||||
"429": *a5
|
||||
@@ -14720,10 +14777,17 @@ paths:
|
||||
tags:
|
||||
- Mental health
|
||||
summary: Record a completed screener
|
||||
description: Stores one administration (item answers encrypted) and writes the derived total as a server-owned
|
||||
PHQ9_SCORE / GAD7_SCORE / WHO5_SCORE / SCI_SCORE measurement (source COMPUTED — never client-writable). WHO-5
|
||||
and SCI totals run HIGHER = better; their follow-up thresholds point downwards (≤ 50 / ≤ 16). On a positive
|
||||
PHQ-9 item-9 the response carries the locale-aware crisis-resource set.
|
||||
description: >-
|
||||
Stores one administration (item answers encrypted) and writes the derived total as a server-owned PHQ9_SCORE /
|
||||
GAD7_SCORE / WHO5_SCORE / SCI_SCORE measurement (source COMPUTED — never client-writable). WHO-5 and SCI totals
|
||||
run HIGHER = better; their follow-up thresholds point downwards (≤ 50 / ≤ 16). On a positive PHQ-9 item-9 the
|
||||
response carries the locale-aware crisis-resource set.
|
||||
|
||||
|
||||
The projection row is the one write this operation makes outside its declared section: the operation is fenced
|
||||
on `mind`, and the measurement it derives lands in `measurements`. It is deliberate and unconditional — a
|
||||
screener whose own total is missing from the record would be an inconsistency inside the section the grant does
|
||||
open. The consent copy for the mood-and-mind section names it.
|
||||
parameters:
|
||||
- *a6
|
||||
requestBody:
|
||||
@@ -29898,8 +29962,8 @@ components:
|
||||
sharing.session.changed, sharing.switch.invalid, sharing.switch.wrong_transport; source-priority —
|
||||
source-priority.body.invalid_json, source-priority.body.invalid_shape; tokens —
|
||||
tokens.measurements.ceiling_reached; vaccination — vaccination.booster-invalid,
|
||||
vaccination.booster-no-antigen, vaccination.encounter-not-found, vaccination.identity-required,
|
||||
vaccination.invalid, vaccination.link.invalid, vaccination.not-found,
|
||||
vaccination.booster-no-antigen, vaccination.booster-out-of-scope, vaccination.encounter-not-found,
|
||||
vaccination.identity-required, vaccination.invalid, vaccination.link.invalid, vaccination.not-found,
|
||||
vaccination.practitioner-not-found; workout — workout.batch.invalid, workout.batch.payload_too_large,
|
||||
workout.batch.too_large; (unprefixed) — about_me_conflict, ai_response_truncated, coach_prefs_conflict,
|
||||
dashboard_layout_conflict, empty_file, health_score_config_conflict, insights_layout_conflict,
|
||||
|
||||
@@ -49,16 +49,16 @@ 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 |
|
||||
| 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, the facts in the health profile, visits and the practices behind them, and the vaccination record |
|
||||
| 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
|
||||
@@ -68,8 +68,12 @@ 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.
|
||||
digest, every insight) read across sections, so a grant naming sections
|
||||
never opens one. A figure derived from part of a record reads as a figure
|
||||
about the person, and it would not be one. Whole-record grants do not all
|
||||
open them equally either: the daily digest and the dashboard's snapshot
|
||||
answer at read, while the health score, the dashboard's summary and the
|
||||
insights need manage.
|
||||
|
||||
A stored scope never grows. A section added by a later release is not in
|
||||
a set somebody ticked before it existed.
|
||||
@@ -80,11 +84,24 @@ 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. |
|
||||
| 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, marking a dose taken or skipped, a visit, a practice, and a vaccination dose. 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. |
|
||||
|
||||
Some entries are creates only **manage** reaches, and the split runs by
|
||||
entry rather than by section: an allergy, a family-history entry, a mood
|
||||
entry, a screener, a cycle day, a water entry and a preventive-care
|
||||
reminder are all created at manage, in sections a write grant can
|
||||
otherwise add to. Nothing in the document vault is writable at any level.
|
||||
|
||||
One entry needs two sections rather than a higher level. Planning the
|
||||
booster a vaccination suggests files it as a preventive-care reminder,
|
||||
which lives under Readings, so a grant that opens Health background alone
|
||||
logs the dose and is refused the booster. Logging the dose is unaffected
|
||||
either way, and the app does not offer the booster to a grant that cannot
|
||||
plan it.
|
||||
|
||||
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
|
||||
@@ -106,12 +123,19 @@ whole feature is built on, and it is drawn on the server:
|
||||
| API tokens, the MCP connector | A durable credential that outlives the access |
|
||||
| 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 |
|
||||
| Full exports, the encrypted archive, clinician share links | A one-click copy of everything, or a new door that outlives the access |
|
||||
| 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 |
|
||||
|
||||
One export is the exception, and it is deliberate: the doctor report
|
||||
(`POST /api/export/health-record`) answers at manage on a whole-record
|
||||
grant. It renders a prepared artefact from a selection the caller declares
|
||||
rather than handing the record over whole, which is the difference the row
|
||||
above is drawing. The full exports and the passphrase-encrypted archive
|
||||
stay with the owner.
|
||||
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user