mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 06:32:03 +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
@@ -13,7 +13,7 @@
|
||||
* they were editing.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Sparkles } from "lucide-react";
|
||||
@@ -87,6 +87,20 @@ export function AiInsightsCard({
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
// v1.16.13 — heal the web AI-consent receipt on mount. The server-side
|
||||
// consent gate (admin-openai egress) requires an active `ai_full`
|
||||
// receipt; the web client never minted one, so existing web users on a
|
||||
// shared-key deployment saw the no-key fallback. This mirrors the iOS
|
||||
// shell-mount heal: idempotent server-side mint (a user with an active
|
||||
// receipt is a no-op), fire-and-forget so a failure never blocks the
|
||||
// settings UI. Runs once per mount of the AI-settings surface.
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) return;
|
||||
void apiFetchRaw("/api/consent/ai/web", { method: "POST" }).catch(() => {
|
||||
/* best-effort heal — the explicit grant path stays available */
|
||||
});
|
||||
}, [isAuthenticated]);
|
||||
|
||||
// The Select is URL-driven so the SSR test can pick the branch and
|
||||
// a deep link works. Default = `?provider=…` query param when the
|
||||
// user navigated here intentionally, else the chain's active entry,
|
||||
|
||||
Reference in new issue
Block a user