Merge remote-tracking branch 'origin/main' into feat/1371-medication-note

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
Marc-André Bombeck committed 2026-11-14 09:36:45 +01:00
commit 4b500dc66c
25 files changed
+1199 -157

No files matched your search

+26 -11
View File
@@ -3617,21 +3617,24 @@ paths:
the only place it ever exists; it is stored as an HMAC and no path re-reveals it.
**What it can do.** `POST /api/measurements` and `POST /api/measurements/batch`, on its owner's own record. Rows
it writes carry `source: EXTERNAL`, resolved from the credential rather than taken from the body, so a reading
pushed by a bridge stays distinguishable from one typed in by hand and can be picked out with the source filter.
A body naming ANY source is refused 422 rather than relabelled — honouring it would defeat the label, overriding
it silently would hand the caller rows it did not ask for. `APPLE_HEALTH` is the one that would do real damage:
it is half a dedup key the phone also writes into, it participates in the cross-source merge, and it is what
**What it can do.** `POST /api/measurements` and `POST /api/measurements/batch`, on its owner's own record, and
`POST /api/custom-metrics/{id}/entries` for a custom metric its owner already created (it cannot list, create,
edit or delete metrics, so the id comes from the metric's page in the app). Measurement rows it writes carry
`source: EXTERNAL`, resolved from the credential rather than taken from the body, so a reading pushed by a
bridge stays distinguishable from one typed in by hand and can be picked out with the source filter. A body
naming ANY source is refused 422 rather than relabelled — honouring it would defeat the label, overriding it
silently would hand the caller rows it did not ask for. `APPLE_HEALTH` is the one that would do real damage: it
is half a dedup key the phone also writes into, it participates in the cross-source merge, and it is what
decides the Apple Health card may claim a sync happened. For the same reason a write through this credential
does not move the native client's sync checkpoint. The rows stay editable by their owner, unlike a connected
provider's — the hardware behind the token is the user's own.
**What it cannot do.** Everything else, including the measurement reads on the same paths, the edit and delete
legs, and the export — a scope grants what it names and nothing adjacent. It cannot be pointed at a shared
record: a request carrying the account selector is refused 403 before any grant is read, whatever grants its
holder actually has. And it cannot mint another token, this endpoint included.
**What it cannot do.** Everything else, including the measurement reads on the same paths, the custom-metric
list and value reads, the edit and delete legs, and the export — a scope grants what it names and nothing
adjacent. It cannot be pointed at a shared record: a request carrying the account selector is refused 403 before
any grant is read, whatever grants its holder actually has. And it cannot mint another token, this endpoint
included.
Minting requires a COOKIE SESSION with a fresh proof: a sign-in or `POST /api/auth/reproof` within the last five
@@ -21140,7 +21143,19 @@ paths:
tags:
- Custom metrics
summary: Log a value
description: Records a value against the custom metric, snapshotting the metric's unit. Audits as `customMetricEntry.create`.
description: >-
Records a value against the custom metric, snapshotting the metric's unit. Audits as `customMetricEntry.create`.
**Narrow token.** Also reachable with a `measurements:write` Bearer (minted at `POST /api/tokens/measurements`),
the supported way to push a custom metric's value from a script or a home-automation rule. Such a credential
writes only to its owner's own live metrics: an id that is unknown, deleted, or another account's answers the
same 404, and a request carrying the account selector is refused 403 before any sharing grant is read. This POST
is all the scope reaches on custom metrics. The value list on this same path, the metric list, and creating,
editing or deleting a metric or a value name no scope and refuse it 403, so the metric id has to come from its
owner: it is the path segment after `/custom-metrics/` in the address of the metric's page in the app. The entry
carries no source of its own; the audit row names the token that wrote it. Every other narrow token,
`workouts:write` included, is refused 403.
parameters:
- in: path
name: id