feat(mental-health): gate the PHQ-9/GAD-7 check-in behind an opt-in module

The mental-wellbeing screener surface shipped built but unreachable: no nav
entry, no module key, ungated. Add a `mentalHealth` module key (opt-in,
default OFF — a depression/anxiety self-assessment is at least as sensitive
as mood) to the registry, the module-hub icon map, and the exhaustive module
maps. Give the check-in a nav home at /insights/mental-wellbeing, born-gated
on the module so it appears only once the account opts in, and redirect the
page home on a direct hit when the module is off.

Item answers stay encrypted and off the AI Coach / MCP regardless of the
toggle; this only controls whether the surface is visible and reachable.
This commit is contained in:
Marc-André Bombeck committed 2026-06-28 18:25:27 +02:00
1 parent 15e740bc2b
commit f62ba50ff0
7 files changed
+88 -7

No files matched your search

+13 -6
View File
@@ -352,12 +352,19 @@ describe("resolveModuleEnabled — mcp module (opt-in, default-OFF)", () => {
});
describe("registry — opt-in marker", () => {
// The opt-in modules ship dark because they open an egress/attack surface:
// `mcp` (remote external-assistant endpoint), `environment` (outbound weather
// fetch tied to a coarse location), and `inboundDocuments` (sends an uploaded
// clinical document to the OCR/vision provider). Every other module is the
// default-on disabled-allowlist.
const OPT_IN_KEYS = new Set(["mcp", "environment", "inboundDocuments"]);
// The opt-in modules ship dark because they open an egress/attack surface or
// are highly sensitive: `mcp` (remote external-assistant endpoint),
// `environment` (outbound weather fetch tied to a coarse location),
// `inboundDocuments` (sends an uploaded clinical document to the OCR/vision
// provider), and `mentalHealth` (a depression / anxiety self-assessment — at
// least as sensitive as mood, surfaced only on explicit opt-in). Every other
// module is the default-on disabled-allowlist.
const OPT_IN_KEYS = new Set([
"mcp",
"environment",
"inboundDocuments",
"mentalHealth",
]);
it("marks only the egress-surface modules opt-in (every other is default-on)", () => {
for (const key of MODULE_KEYS) {
+20
View File
@@ -136,6 +136,14 @@ export const MODULE_KEYS = [
// `optIn` marker on its registry entry inverts the per-user default;
// everything else reuses the existing module machinery unchanged.
"inboundDocuments",
// v1.25.0 — opt-in mental-health screeners (PHQ-9 / GAD-7), beside mood
// tracking. OPT-IN (off by default): a depression / anxiety self-assessment
// is at least as sensitive as mood, so it ships dark and the user turns it on
// deliberately. The `optIn` marker inverts the per-user default; with it off
// the `/insights/mental-wellbeing` surface is hidden from nav and the
// assessment routes refuse server-side. Item answers are always encrypted and
// always excluded from the AI Coach / MCP regardless of this toggle.
"mentalHealth",
] as const;
export type ModuleKey = (typeof MODULE_KEYS)[number];
@@ -298,6 +306,18 @@ export const MODULE_REGISTRY: Readonly<Record<ModuleKey, ModuleDefinition>> =
// configured OCR/vision provider, so the user turns it on deliberately.
optIn: true,
},
mentalHealth: {
key: "mentalHealth",
labelKey: "modules.mentalHealth.label",
descriptionKey: "modules.mentalHealth.description",
category: "tracking",
// Off by default: a depression / anxiety screener is highly sensitive, so
// the surface ships dark and the user opts in deliberately. Turning it on
// reveals the `/insights/mental-wellbeing` check-in and lets the screener
// total ride the doctor-report export; item answers stay encrypted and
// off the AI Coach / MCP regardless.
optIn: true,
},
});
const MODULE_KEY_SET: ReadonlySet<string> = new Set(MODULE_KEYS);