From cbc428c89bc152f14b8bca7476aae338bb471bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 27 Jun 2026 00:33:07 +0200 Subject: [PATCH] fix(insights): gate the ambient Coach routes on the Coach assistant matrix The metric-page "Coach read" strip and the unscoped-hero seeded opener are Coach surfaces, so both routes now call requireAssistantSurface("coach") alongside the insights module gate. With the Coach turned off they return the same refusal every other Coach route does, instead of lingering as a "Coach read" line while the assistant is disabled. Restores the coach route-gate inventory invariant. --- src/app/api/insights/coach-read/route.ts | 6 ++++++ src/app/api/insights/coach/seeded-question/route.ts | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/app/api/insights/coach-read/route.ts b/src/app/api/insights/coach-read/route.ts index 149f0d951..82fe83c57 100644 --- a/src/app/api/insights/coach-read/route.ts +++ b/src/app/api/insights/coach-read/route.ts @@ -20,6 +20,7 @@ import { apiHandler, requireAuth } from "@/lib/api-handler"; import { annotate } from "@/lib/logging/context"; import { checkAnalyticsReadRateLimit } from "@/lib/rate-limit"; import { requireModuleEnabled } from "@/lib/modules/gate"; +import { requireAssistantSurface } from "@/lib/feature-flags"; import { measurementTypeEnum } from "@/lib/validations/measurement"; import { buildCoachReadStrip } from "@/lib/insights/derived/coach-read"; import type { MeasurementType } from "@/generated/prisma/client"; @@ -36,6 +37,11 @@ export const GET = apiHandler(async (request: NextRequest) => { const m = await requireModuleEnabled(user.id, "insights"); if (!m.enabled) return m.response; + // The strip is the ambient Coach presence on the metric page, so it + // gates on the Coach assistant matrix too: an operator who turns the + // Coach off must not see a "Coach read" line linger. + await requireAssistantSurface("coach"); + // Shared analytics-read budget — generous; caps a runaway navigation loop. const rl = await checkAnalyticsReadRateLimit(user.id); if (!rl.allowed) { diff --git a/src/app/api/insights/coach/seeded-question/route.ts b/src/app/api/insights/coach/seeded-question/route.ts index 42bc23ad9..6fdba6eb7 100644 --- a/src/app/api/insights/coach/seeded-question/route.ts +++ b/src/app/api/insights/coach/seeded-question/route.ts @@ -22,6 +22,7 @@ import { apiHandler, requireAuth } from "@/lib/api-handler"; import { annotate } from "@/lib/logging/context"; import { checkAnalyticsReadRateLimit } from "@/lib/rate-limit"; import { requireModuleEnabled } from "@/lib/modules/gate"; +import { requireAssistantSurface } from "@/lib/feature-flags"; import { prisma } from "@/lib/db"; import { loadBaselineProfile } from "@/lib/insights/derived"; import { detectDerivedBriefingSignals } from "@/lib/insights/derived-briefing"; @@ -50,6 +51,10 @@ export const GET = apiHandler(async () => { const m = await requireModuleEnabled(user.id, "insights"); if (!m.enabled) return m.response; + // The opener feeds the Coach hero, so it gates on the Coach assistant + // matrix: with the Coach off there is no hero to seed. + await requireAssistantSurface("coach"); + const rl = await checkAnalyticsReadRateLimit(user.id); if (!rl.allowed) { return apiError("Too many analytics requests. Please retry later.", 429);