mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
feat(consent): mint AI consent receipt from the web client
The server-side gate requires an active ConsentReceipt before any health snapshot egresses via the operator's server-managed key, but the web client never minted one — so web users on a shared-key deployment hit the fail-closed no-key fallback on every AI surface. Add ensureWebAiConsentReceipt() and POST /api/consent/ai/web: an idempotent ai_full grant mirroring the iOS master grant (a user with an active receipt is a no-op). The AI-settings card calls it on mount — the web equivalent of the iOS shell-mount heal — so existing accounts gain a receipt without a re-consent step. Surface an honest signal: resolveReadOnlyStatusMiss now returns a consent-missing outcome when a provider is configured but the gate would block egress, distinct from no-provider, and skips the wasted enqueue. Document the receipt precondition + the consent surface in the OpenAPI contract (it shipped without a bump): the /api/consent/ai family, a Consent tag, and a 403 consent.ai.required response on the AI-egress route.
This commit is contained in:
1 parent
7fa10b43a0
commit
f9876f9492
19 files changed
+563
-12
No files matched your search
+181
-1
@@ -32,6 +32,7 @@ tags:
|
||||
- name: Mood
|
||||
- name: Analytics
|
||||
- name: Insights
|
||||
- name: Consent
|
||||
- name: Dashboard
|
||||
- name: Notifications
|
||||
- name: Devices
|
||||
@@ -3979,7 +3980,8 @@ paths:
|
||||
- Insights
|
||||
summary: Comprehensive AI insights bundle
|
||||
description: Full Insights surface — daily briefing, recommendations with rationale, optional weekly report + storyboard
|
||||
annotations. Strict-schema validated server-side.
|
||||
annotations. Strict-schema validated server-side. Requires an active ConsentReceipt when the resolved provider
|
||||
chain egresses via the operator's server-managed key (see POST /api/consent/ai).
|
||||
responses:
|
||||
"200":
|
||||
description: Insights bundle.
|
||||
@@ -3988,6 +3990,13 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/InsightsComprehensiveResponseEnvelope"
|
||||
"401": *a1
|
||||
"403":
|
||||
description: "AI consent required: no active ConsentReceipt for the server-managed provider. `meta.errorCode` =
|
||||
`consent.ai.required`. Mint a receipt via POST /api/consent/ai before retrying."
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/insights/pregenerate:
|
||||
@@ -4866,6 +4875,72 @@ paths:
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/consent/ai:
|
||||
post:
|
||||
tags:
|
||||
- Consent
|
||||
summary: Grant AI consent (signed artefact)
|
||||
description: Persist a fresh AI-consent receipt. Required before server-managed AI egress (the operator's global LLM
|
||||
key); BYOK / local / ChatGPT-OAuth chains are the user's own egress and are not gated. Append-only audit trail.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
enum:
|
||||
- ai_full
|
||||
- ai_insights_only
|
||||
- ai_coach
|
||||
artefact:
|
||||
type: string
|
||||
minLength: 1
|
||||
signedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||||
required:
|
||||
- kind
|
||||
- artefact
|
||||
- signedAt
|
||||
responses:
|
||||
"200":
|
||||
description: The minted receipt.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ConsentReceiptResponseEnvelope"
|
||||
"400":
|
||||
description: Request validation failed (consent routes use 400).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ConsentValidationErrorEnvelope"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/consent/ai/web:
|
||||
post:
|
||||
tags:
|
||||
- Consent
|
||||
summary: Grant web AI consent (idempotent ai_full)
|
||||
description: Mint an `ai_full` consent receipt for the calling web user if none is active; a no-op when one already
|
||||
exists. Mirrors the iOS master grant; the web AI-settings surface calls this on mount so existing web accounts
|
||||
on a shared-key deployment gain a receipt without a re-consent step. Revocation flows through DELETE
|
||||
/api/consent/ai/latest.
|
||||
responses:
|
||||
"200":
|
||||
description: Grant outcome.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/WebConsentGrantResponseEnvelope"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
components:
|
||||
schemas:
|
||||
PasskeyLoginVerifyRequest:
|
||||
@@ -14317,6 +14392,111 @@ components:
|
||||
additionalProperties: false
|
||||
description: "Medication reminder thresholds: the operator-level late/missed minute marks plus the calling user's
|
||||
low-stock runway threshold (v1.16.11)."
|
||||
ConsentReceiptResponseEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/ConsentReceiptResponse"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
ConsentReceiptResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
receipt:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
userId:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
enum:
|
||||
- ai_full
|
||||
- ai_insights_only
|
||||
- ai_coach
|
||||
signedAt:
|
||||
type: string
|
||||
revokedAt:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
createdAt:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- userId
|
||||
- kind
|
||||
- signedAt
|
||||
- revokedAt
|
||||
- createdAt
|
||||
additionalProperties: false
|
||||
required:
|
||||
- id
|
||||
- receipt
|
||||
additionalProperties: false
|
||||
description: The minted receipt. `artefact` is deliberately stripped from the response — it is opaque and only read at
|
||||
audit time directly from the DB.
|
||||
ConsentValidationErrorEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: "null"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
WebConsentGrantResponseEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/WebConsentGrantResponse"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
WebConsentGrantResponse:
|
||||
type: object
|
||||
properties:
|
||||
minted:
|
||||
type: boolean
|
||||
description: True when a new `ai_full` receipt was minted; false when an active one already existed (idempotent no-op).
|
||||
kind:
|
||||
type: string
|
||||
const: ai_full
|
||||
required:
|
||||
- minted
|
||||
- kind
|
||||
additionalProperties: false
|
||||
description: Outcome of the idempotent web `ai_full` grant. Safe to call on every AI-settings mount.
|
||||
MedicationScheduleOutput:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in new issue
Block a user