mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
Data never depends on AI. The Insights data reads no longer ask an AI switch or the `insights` module (now only the AI analysis opt-out): ECG list, detail and upload, rhythm events, derived scores, rule cards, correlations and patterns, the baseline strip, health status, intraday pulse, and the Today digest with its dismiss. Breathing screening moves to the sleep module and lab changes to the labs module. Stored Coach facts, conversations, message ratings and the seen stamp stay readable and erasable while the Coach is unavailable. Cards now say `provider: "rules"`. Reads that mix data with model text answer 200, null the model text and publish an `ai` state from the capability resolver, without warming anything: comprehensive (`ai.briefing`, `ai.statusText`, `hasProvider` from the presence probe), the derived assessment override, the period narrative, nudge status, the seeded opener, about-me questions, the workout paragraph, the pregenerate warm (`queued: false`), the Today digest (lead, reaction line, check-in) and the dashboard briefing, which is now applied per read after the snapshot cache. Hiding the Coach no longer hides the briefing. `insights/generate` GET and POST ask the `briefing` capability and `insights/chat` POST the `coach` capability, with the typed refusal envelope; a missing provider keeps the chat's `coach.provider.none` frame and adds `reason`. The /coach page, the MCP ECG and intraday tools and `aiAvailable` on the provider route follow the same answer. An inventory of AI and data routes replaces the Coach gate inventory: every route that asks a capability names it, and no data route asks. Refs #975
38 lines
2.1 KiB
TypeScript
38 lines
2.1 KiB
TypeScript
/**
|
|
* The published wording for an AI capability refusal, shared by every
|
|
* operation that asks `requireAiCapability`.
|
|
*
|
|
* One envelope, three statuses: 403 for the operator, record, module, opt-out
|
|
* and consent reasons; 422 for a missing provider (unless the route keeps a
|
|
* typed code of its own); 503 when the capability could not be resolved. The
|
|
* mapping from reason to `meta.errorCode` is written once on `ErrorEnvelope`
|
|
* (`meta.reason`); these strings name the capability and point there.
|
|
*/
|
|
import {
|
|
AI_CAPABILITIES,
|
|
type AiCapabilityKey,
|
|
} from "@/lib/ai/capabilities/types";
|
|
|
|
import { errorEnvelope } from "./shared";
|
|
|
|
/** The 403 sentence for one capability. */
|
|
export function aiRefusal403Description(key: AiCapabilityKey): string {
|
|
const operatorSwitch = AI_CAPABILITIES[key].operatorSwitch;
|
|
return `The \`${key}\` AI capability is unavailable for this record. \`meta.capability\` = \`${key}\` and \`meta.reason\` says which layer refused: the operator's switch (\`assistant.disabled.${operatorSwitch}\`, or \`assistant.disabled.enabled\` for the master switch), an owning module or the person's own AI opt-out (\`module.disabled\` with \`meta.module\`), a delegate inside somebody else's record (\`ai.record.notPermitted\`), or a missing consent receipt (\`consent.ai.required\`). Nothing was generated, read from a stored model answer, or enqueued.`;
|
|
}
|
|
|
|
/** The 422 a capability answers when no provider can serve it. */
|
|
export function aiNoProviderResponse(key: AiCapabilityKey) {
|
|
return {
|
|
description: `No configured AI provider can serve the \`${key}\` capability: \`meta.errorCode\` = \`ai.provider.none\`, \`meta.reason\` = \`no_provider\`.`,
|
|
content: { "application/json": { schema: errorEnvelope } },
|
|
};
|
|
}
|
|
|
|
/** The 503 every capability answers when its inputs could not be read. */
|
|
export const aiCheckFailedResponse = {
|
|
description:
|
|
"The AI capability could not be resolved (its inputs could not be read) and the answer failed closed: `meta.errorCode` = `ai.unavailable`, `meta.reason` = `check_failed`. Retry later; no data depends on it.",
|
|
content: { "application/json": { schema: errorEnvelope } },
|
|
};
|