mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
fix(admin): lift the bench when the operator fixes the shared provider
The health ledger records failures per account and provider, and since background work leaves out a benched provider, an operator who fixed the instance-wide provider in the admin AI settings still saw every account's background work sit out the pause the broken setup had earned, up to a day. Only a person's own provider settings and the Codex sign-ins forgot the record. A successful save of the admin AI settings now forgets what the ledger recorded for admin-openai on every account and closes the status notes' negative windows, the same reset a person's own save makes for theirs (clearStatusNegativeWindows without an account covers every record). The ledger comments that still said a benched row only deprioritises now say what it does for background work. Refs #1279
This commit is contained in:
1 parent
f1163d5efb
commit
402a4a546c
5 files changed
+72
-16
No files matched your search
@@ -16,6 +16,10 @@ vi.mock("@/lib/db", () => ({
|
||||
upsert: vi.fn(),
|
||||
},
|
||||
user: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
providerHealth: { deleteMany: vi.fn().mockResolvedValue({ count: 0 }) },
|
||||
insightStatusCache: {
|
||||
updateMany: vi.fn().mockResolvedValue({ count: 0 }),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -217,6 +221,31 @@ describe("PUT /api/admin/ai-settings", () => {
|
||||
statusCode: 429,
|
||||
});
|
||||
});
|
||||
|
||||
// #1279 — background work leaves a provider the health ledger benched out
|
||||
// of every account's chain. A fixed operator provider must not sit out a
|
||||
// pause the broken one earned, so a saved change forgets what the ledger
|
||||
// recorded for it on every account, and lets their notes try again.
|
||||
it("forgets the operator provider's recorded failures on every account", async () => {
|
||||
upsertEcho();
|
||||
await PUT(jsonReq({ model: "gpt-4o-mini" }));
|
||||
expect(prisma.providerHealth.deleteMany).toHaveBeenCalledWith({
|
||||
where: { providerType: "admin-openai" },
|
||||
});
|
||||
expect(prisma.insightStatusCache.updateMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
OR: [{ retryAt: { not: null } }, { failureStreak: { gt: 0 } }],
|
||||
},
|
||||
data: { retryAt: null, negativeReason: null, failureStreak: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("forgets nothing when the change is refused", async () => {
|
||||
await expect(
|
||||
PUT(jsonReq({ baseUrl: "https://attacker.example/v1" })),
|
||||
).rejects.toMatchObject({ statusCode: 422 });
|
||||
expect(prisma.providerHealth.deleteMany).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,8 @@ import { encrypt, decrypt } from "@/lib/crypto";
|
||||
import { checkRateLimit } from "@/lib/rate-limit";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { legacyPrivateHostReport } from "@/lib/ai/legacy-private-host-report";
|
||||
import { forgetProviderHealth } from "@/lib/ai/provider-health-ledger";
|
||||
import { clearStatusNegativeWindows } from "@/lib/insights/status-negative-window";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -134,6 +136,16 @@ export const PUT = apiHandler(async (request: NextRequest) => {
|
||||
details: auditDetails,
|
||||
});
|
||||
|
||||
// #1279 — background work leaves out a provider the health ledger benched,
|
||||
// on every account whose chain falls back on this one, and a status note
|
||||
// waits out its negative window. A fixed operator provider must not sit out
|
||||
// a pause the broken one earned, so what was recorded against it is
|
||||
// forgotten on every account, as a person's own save does for theirs.
|
||||
await Promise.all([
|
||||
forgetProviderHealth({ providerType: "admin-openai" }),
|
||||
clearStatusNegativeWindows(),
|
||||
]);
|
||||
|
||||
const hasKey = Boolean(settings.adminAiKeyEncrypted);
|
||||
|
||||
return apiSuccess({
|
||||
|
||||
Reference in new issue
Block a user