feat(insights): let the usage level decide the background AI work

The level now sets, per record: the status-card regeneration gap after a
sync (frugal 24 h, standard the operator's gap, full 1 h) and after a hand
entry (frugal 6 h, otherwise 1 h); the status snapshot cap (10 000 compact
characters on frugal, 14 000 above); background reasoning for the briefing
and the period narratives (off on frugal); the daily briefing's form (a
short form on frugal that keeps every field and at most one
recommendation, the full contract on standard and full); and whether the
automatic document read may act (frugal reads on request only, the stored
switch is kept).

A deterministic simulation of a watch-synced day through the invalidator
counts 122 ingest-driven status generations on full (the v1.42 behaviour),
25 on standard and 2 on frugal, plus the nightly warm on each.

Refs #1217
This commit is contained in:
Marc-André Bombeck committed 2026-10-15 22:55:40 +02:00
1 parent d3770b9010
commit 74dcc67ef7
23 files changed
+840 -83

No files matched your search

@@ -0,0 +1,64 @@
/**
* #1217 — the automatic document read acts only while both the person's
* switch is on and their AI usage level allows it. On `frugal` documents are
* read on request only; the stored switch is left alone.
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
const db = vi.hoisted(() => ({
user: { documentsAutoAiRead: true, aiUsageLevel: null as string | null },
settings: {
aiUsageMaxLevel: "FULL",
aiUsageDefaultLevel: "STANDARD",
} as Record<string, unknown> | null,
failSettings: false,
}));
vi.mock("@/lib/db", () => ({
prisma: {
user: { findUnique: vi.fn(async () => db.user) },
appSettings: {
findUnique: vi.fn(async () => {
if (db.failSettings) throw new Error("db down");
return db.settings;
}),
},
},
}));
vi.mock("@/lib/logging/context", () => ({
annotate: vi.fn(),
getEvent: () => undefined,
}));
import { documentAutoReadEnabled } from "../document-settings";
beforeEach(() => {
db.user = { documentsAutoAiRead: true, aiUsageLevel: null };
db.settings = { aiUsageMaxLevel: "FULL", aiUsageDefaultLevel: "STANDARD" };
db.failSettings = false;
});
describe("documentAutoReadEnabled", () => {
it.each([
["FRUGAL", false],
["STANDARD", true],
["FULL", true],
[null, true],
])("level %s with the switch on reads %s", async (level, expected) => {
db.user = { documentsAutoAiRead: true, aiUsageLevel: level };
expect(await documentAutoReadEnabled("u1")).toBe(expected);
});
it("stays off with the switch off, whatever the level", async () => {
db.user = { documentsAutoAiRead: false, aiUsageLevel: "FULL" };
expect(await documentAutoReadEnabled("u1")).toBe(false);
});
it("rests on a frugal instance default", async () => {
db.settings = {
aiUsageMaxLevel: "STANDARD",
aiUsageDefaultLevel: "FRUGAL",
};
expect(await documentAutoReadEnabled("u1")).toBe(false);
});
});
+18 -4
View File
@@ -12,13 +12,21 @@
* - the summary job and its catch-up pass run only while it is ON;
* - the auto-index-on-upload job stays strictly local when it is OFF and uses
* the document-order external pick when it is ON.
*
* v1.43 (#1217) — on the `frugal` AI usage level the setting rests: documents
* are read on request only, whatever the switch says, and the switch acts
* again as soon as the level allows it. The stored choice is never changed.
*/
import { prisma } from "@/lib/db";
import { loadRecordUsageLevel } from "@/lib/ai/capabilities/usage";
import { AI_USAGE_LEVEL_EFFECTS } from "@/lib/ai/usage-level";
/**
* True when the user has opted into automatic AI reading of uploaded documents.
* A missing row (deleted account raced against a job) resolves to `false` —
* fail-closed to the local-first posture, never to external egress.
* True when the user has opted into automatic AI reading of uploaded documents
* and their AI usage level lets the opt-in act. A missing row (deleted account
* raced against a job) resolves to `false` — fail-closed to the local-first
* posture, never to external egress; so does a usage level that cannot be
* read.
*/
export async function documentAutoReadEnabled(
userId: string,
@@ -27,5 +35,11 @@ export async function documentAutoReadEnabled(
where: { id: userId },
select: { documentsAutoAiRead: true },
});
return row?.documentsAutoAiRead ?? false;
if (!(row?.documentsAutoAiRead ?? false)) return false;
try {
const level = await loadRecordUsageLevel(userId);
return AI_USAGE_LEVEL_EFFECTS[level].documentsAutoRead;
} catch {
return false;
}
}