From 397423dddc261c5876e79889db581ff6443243dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 18 Jun 2026 22:41:57 +0200 Subject: [PATCH] docs(api): register the onboarding disclaimer endpoint in the contract The one-time disclaimer acknowledgment route shipped without an OpenAPI entry, leaving a gap in the contract the iOS client consumes. Add the POST path and regenerate the spec. --- docs/api/openapi.yaml | 63 ++++++++++++++++++++++++++++ src/lib/openapi/routes/onboarding.ts | 52 +++++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index a37716af5..166748f5d 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -6861,6 +6861,30 @@ paths: "401": *a1 "422": *a3 "429": *a4 + /api/onboarding/disclaimer: + post: + tags: + - Onboarding + summary: Acknowledge the one-time medical disclaimer + description: "Stamps the user's medical-disclaimer acknowledgment. Idempotent: a repeat acknowledgment of the same + version refreshes the timestamp. The body version is a freshness signal so a stale shell cannot record copy it + never rendered; the server persists its own canonical version." + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/DisclaimerAckRequest" + responses: + "200": + description: Disclaimer acknowledged. + content: + application/json: + schema: + $ref: "#/components/schemas/DisclaimerAckEnvelope" + "401": *a1 + "422": *a3 + "429": *a4 /api/onboarding/tour: post: tags: @@ -8225,6 +8249,18 @@ components: type: string required: - ids + DisclaimerAckRequest: + type: object + properties: + version: + type: string + minLength: 1 + maxLength: 64 + description: The disclaimer copy version the client rendered. A freshness signal only — the server pins and persists its + own canonical version. + required: + - version + description: Acknowledge the one-time medical disclaimer shown at onboarding. TourUpdateRequest: type: object properties: @@ -19681,6 +19717,33 @@ components: description: "Cross-episode retrospective summary over the trailing window: episode + resolved counts, the typical (median) recovery gap (null below the min-sample floor — withholds a thin claim), a recurrence-by-month tally, and a per-type breakdown. Retrospective only." + DisclaimerAckEnvelope: + type: object + properties: + data: + $ref: "#/components/schemas/DisclaimerAckResponse" + error: + type: "null" + meta: + type: object + properties: + requestId: + type: string + additionalProperties: false + required: + - data + - error + additionalProperties: false + DisclaimerAckResponse: + type: object + properties: + acknowledgedVersion: + type: string + description: The canonical disclaimer version the server stamped. + required: + - acknowledgedVersion + additionalProperties: false + description: The persisted disclaimer acknowledgment version. TourUpdateEnvelope: type: object properties: diff --git a/src/lib/openapi/routes/onboarding.ts b/src/lib/openapi/routes/onboarding.ts index efdc1b4cf..7f57fb4dd 100644 --- a/src/lib/openapi/routes/onboarding.ts +++ b/src/lib/openapi/routes/onboarding.ts @@ -54,7 +54,59 @@ const tourUpdateResponse = z description: "The persisted completion flag and resume point after the write.", }); +const disclaimerAckRequest = z + .object({ + version: z + .string() + .min(1) + .max(64) + .describe( + "The disclaimer copy version the client rendered. A freshness signal only — the server pins and persists its own canonical version.", + ), + }) + .meta({ + id: "DisclaimerAckRequest", + description: "Acknowledge the one-time medical disclaimer shown at onboarding.", + }); + +const disclaimerAckResponse = z + .object({ + acknowledgedVersion: z + .string() + .describe("The canonical disclaimer version the server stamped."), + }) + .meta({ + id: "DisclaimerAckResponse", + description: "The persisted disclaimer acknowledgment version.", + }); + export const onboardingPaths: NonNullable = { + "/api/onboarding/disclaimer": { + post: { + tags: ["Onboarding"], + summary: "Acknowledge the one-time medical disclaimer", + description: + "Stamps the user's medical-disclaimer acknowledgment. Idempotent: a repeat acknowledgment of the same version refreshes the timestamp. The body version is a freshness signal so a stale shell cannot record copy it never rendered; the server persists its own canonical version.", + requestBody: { + required: true, + content: { "application/json": { schema: disclaimerAckRequest } }, + }, + responses: { + "200": { + description: "Disclaimer acknowledged.", + content: { + "application/json": { + schema: dataEnvelope( + disclaimerAckResponse, + "DisclaimerAckEnvelope", + ), + }, + }, + }, + ...stdResponses, + }, + }, + }, "/api/onboarding/tour": { post: { tags: ["Onboarding"],