docs(api): correct two comments that describe behaviour the code does not have

The single-record write ceiling justified itself with "every one of these
routes is already wrapped in `withIdempotency`". Ten of the eleven are.
`POST /api/medications/intake` is not, and it is the one of the eleven a
native client actually loops — it is replay-tolerant for its own reason,
being an update of a named `intakeId` rather than a create. The comment
now says which is which, and adds the clause it was missing: the check
runs before the body is read, so a refused request spends the allowance
too.

The custom-symptom wrapper said a replay "answered the 409 that
uniqueness raises". There is no such 409. The row's key is minted fresh
per request and the label lives in an AES-GCM column with a per-write
IV, which can carry no unique index, so an un-keyed replay wrote a
second row with the same label — a silent duplicate in the person's own
vocabulary, up to the fifty-row cap. The wrapper is worth more than the
old sentence claimed, not less.
This commit is contained in:
Marc-André Bombeck committed 2026-09-10 08:38:16 +02:00
1 parent b2c8fe8567
commit 68964b4bac
2 files changed
+17 -7

No files matched your search

+9 -4
View File
@@ -70,10 +70,15 @@ export const GET = apiHandler(async () => {
/**
* Wrapped in `withIdempotency`: a custom symptom is minted from the client's
* offline outbox under the same `Idempotency-Key`, and a replay after a lost
* success response would otherwise answer the 409 the label's unique key
* raises rather than the 201 the first attempt produced — a create the client
* has no record of, reported as a conflict it cannot act on.
* offline outbox under the same `Idempotency-Key`, and nothing else here would
* catch the replay. The row's key is minted fresh per request
* (`custom:<uuid>`), and the label it would duplicate lives in
* `labelEncrypted` — AES-GCM with a per-write IV, so no unique index can be
* put on it. A replay after a lost success response therefore wrote a SECOND
* row carrying the same label, up to the fifty-row cap: a silent duplicate in
* the person's own symptom vocabulary rather than a conflict anything refused.
* The wrapper answers the first attempt's 201 instead, and the in-flight 409 it
* defines is the only 409 this route can produce.
*/
export const POST = apiHandler(
withIdempotency<[NextRequest]>(postCustomSymptom),
+8 -3
View File
@@ -132,9 +132,14 @@ export async function checkAnalyticsReadRateLimit(
* `checkAnalyticsReadRateLimit` gives: a loop that rotates across ten creates
* should still meet a cap. Three hundred a minute is five writes a second
* sustained for a full minute — no hand-driven session and no sync burst
* reaches it, and every one of these routes is already wrapped in
* `withIdempotency`, so an honest retry storm is deduplicated before it is
* counted. It exists to cut off the pathological loop, not to shape traffic.
* reaches it. Ten of the eleven are wrapped in `withIdempotency`, so an honest
* retry storm is deduplicated before it is counted; `POST
* /api/medications/intake` is not, because it updates a named `intakeId`
* rather than creating a row, so a replay writes the status that is already
* there. It exists to cut off the pathological loop, not to shape traffic.
*
* A refused request is counted too: the check runs before the body is read,
* so a client looping on a 422 spends its allowance without writing a row.
*
* Keyed on the ACTING account rather than the record, the frozen precedent from
* the delegable medication routes: a delegate burns their own allowance rather