fix(onboarding): no shared-provider offer where the grant cannot be made

The done screen's one-tap button posts `POST /api/consent/ai/web`, a path the
demo instance's edge allowlist refuses — correctly, since the demo is a single
shared account and a receipt one visitor minted would turn the operator's
provider on for everyone after them. The button was offered anyway, so a
visitor who took it got the generic "give it another try in a moment" toast on
the one screen whose whole purpose is not to say things it cannot back.

The route withholds the offer under `DEMO_MODE`, which leaves the panel in its
neutral form, claiming nothing either way.
This commit is contained in:
Marc-André Bombeck committed 2026-09-11 10:58:25 +02:00
1 parent 97174f470d
commit ba29a5e471
4 files changed
+33 -2

No files matched your search

@@ -563,4 +563,26 @@ describe("GET /api/user/ai-provider — the shared-provider offer", () => {
expect(vi.mocked(readServerProviderHealth)).toHaveBeenCalledTimes(1);
expect(vi.mocked(readServerProviderHealth)).toHaveBeenCalledWith();
});
it("never offers on a demo instance, where the grant cannot be made", async () => {
// The one tap posts `POST /api/consent/ai/web`, which the demo's edge
// allowlist refuses — and should: the demo is one shared account, so a
// receipt minted by one visitor would turn the operator's provider on
// for every later one. Offering a button that can only 403 into a
// generic toast is the misleading failure this screen exists to remove,
// so the offer is withheld and the screen falls to its neutral panel.
const previous = process.env.DEMO_MODE;
process.env.DEMO_MODE = "true";
try {
serverManaged();
vi.mocked(readServerProviderHealth).mockResolvedValue("healthy");
await expect(read()).resolves.toMatchObject({
serverProviderHealth: "healthy",
serverProviderOffer: false,
});
} finally {
if (previous === undefined) delete process.env.DEMO_MODE;
else process.env.DEMO_MODE = previous;
}
});
});
+8
View File
@@ -87,7 +87,15 @@ export const GET = apiHandler(async () => {
providerWorkAuthorityForRecord(user.id),
u?.managedProfileAt ?? null,
);
// A demo instance can show the offer but cannot honour it: the one tap
// posts `POST /api/consent/ai/web`, which the demo's edge allowlist
// refuses — and should, since the demo is one shared account and a receipt
// one visitor minted would turn the operator's provider on for every later
// one. A button that can only 403 into a generic toast is precisely the
// misleading failure this surface exists to remove, so there is no button.
const demoInstance = process.env.DEMO_MODE === "true";
const serverProviderOffer =
!demoInstance &&
managedBy === "server" &&
serverProviderHealth === "healthy" &&
assistantFlags.enabled &&