mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 14:32:03 +00:00
fix(idempotency): replay a key only for the same request body
The replay cell was keyed on (user, key, method, path). A client that sends two different writes under one key got the first write's cached response for the second, and the second was never saved. Native clients up to 1.1.0 post the two halves of a manual blood pressure exactly that way, so the diastolic value was silently dropped. Each cell now stores a SHA-256 fingerprint of the request body (JSON with recursively sorted keys, otherwise the raw bytes; scoped by method and path; at most the first MiB is read). Lookup replays, or refuses as in flight, only when the fingerprint matches. A different body, or a row written before the column existed, runs the handler without claiming or caching and leaves the existing cell for its own retries; a lost claim race re-checks the winner's fingerprint the same way. The body itself is never stored. Migration 0371 adds the nullable idempotency_keys.request_fingerprint column. The OpenAPI parameter description, the client notes and the Home Assistant guide say what a replay now requires. docs/ops gains a read-only query that lists systolic readings left without their diastolic half, held to the schema by an integration test.
This commit is contained in:
1 parent
808b2672cd
commit
b55850967d
12 files changed
+761
-22
No files matched your search
@@ -162,12 +162,14 @@ paths:
|
||||
pattern: ^[A-Za-z0-9_\-:.]{8,128}$
|
||||
minLength: 8
|
||||
maxLength: 128
|
||||
description: "Makes this write safe to retry. Send the same key again and the first response is replayed rather than the
|
||||
work repeated, marked with `X-Idempotent-Replay: true`. A key that does not match the pattern is IGNORED
|
||||
rather than refused — the write then proceeds unprotected and looks exactly like a request that carried no
|
||||
key, so validate the key on your side. Scoped per user and per (method, path): the same key on a different
|
||||
route is a different key. While a first request under this key is still in flight, a second is refused with
|
||||
409 rather than queued."
|
||||
description: "Makes this write safe to retry. Send the same key with the same body again and the first response is
|
||||
replayed rather than the work repeated, marked with `X-Idempotent-Replay: true`. The body is compared as
|
||||
JSON with key order ignored; a different body under the same key is not a retry and runs as a new write,
|
||||
while the first response stays stored for retries of the first request. A key that does not match the
|
||||
pattern is IGNORED rather than refused — the write then proceeds unprotected and looks exactly like a
|
||||
request that carried no key, so validate the key on your side. Scoped per user and per (method, path): the
|
||||
same key on a different route is a different key. While a first request under this key is still in flight, a
|
||||
second with the same body is refused with 409 rather than queued."
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
|
||||
Reference in new issue
Block a user