From adb932052c611aa8ff5ff98eff147cc7197ed699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 6 Aug 2026 01:53:04 +0200 Subject: [PATCH] fix: keep managed system work operator-scoped Resolve managed record credentials through the operator policy for system work. --- src/lib/ai/provider.ts | 54 +++++++++++-------- src/lib/sharing/provider-work-authority.ts | 4 ++ .../sharing-provider-origin.test.ts | 42 ++++++++++++++- 3 files changed, 76 insertions(+), 24 deletions(-) diff --git a/src/lib/ai/provider.ts b/src/lib/ai/provider.ts index e90e58ba4..33e2228fb 100644 --- a/src/lib/ai/provider.ts +++ b/src/lib/ai/provider.ts @@ -405,11 +405,8 @@ async function resolveAdminCodexProvider(): Promise { * 4. Nothing configured → NoProvider(). */ export async function resolveProvider(userId: string): Promise { - const policy = providerCredentialPolicy( - providerWorkAuthorityForRecord(userId), - ); - if (policy === "deny") return new NoProvider(); - if (policy === "operator-default") return resolveAdminProvider(); + const authority = providerWorkAuthorityForRecord(userId); + if (providerCredentialPolicy(authority) === "deny") return new NoProvider(); const userRow = await prisma.user.findUnique({ where: { id: userId }, @@ -423,8 +420,14 @@ export async function resolveProvider(userId: string): Promise { aiCompatBaseUrl: true, aiCompatKeyEncrypted: true, aiCompatModel: true, + managedProfileAt: true, }, }); + const policy = providerCredentialPolicy( + authority, + userRow?.managedProfileAt ?? null, + ); + if (policy === "operator-default") return resolveAdminProvider(); // 1. Per-user Anthropic / Local if (userRow) { @@ -470,16 +473,8 @@ export async function resolveProvider(userId: string): Promise { export async function resolveProviderChain( userId: string, ): Promise { - const policy = providerCredentialPolicy( - providerWorkAuthorityForRecord(userId), - ); - if (policy === "deny") return []; - if (policy === "operator-default") { - const provider = await resolveAdminProvider(); - return provider.type === "none" - ? [] - : [{ providerType: "admin-openai", instance: provider }]; - } + const authority = providerWorkAuthorityForRecord(userId); + if (providerCredentialPolicy(authority) === "deny") return []; const userRow = await prisma.user.findUnique({ where: { id: userId }, @@ -495,8 +490,19 @@ export async function resolveProviderChain( aiCompatModel: true, aiProviderChain: true, useCentralCodex: true, + managedProfileAt: true, }, }); + const policy = providerCredentialPolicy( + authority, + userRow?.managedProfileAt ?? null, + ); + if (policy === "operator-default") { + const provider = await resolveAdminProvider(); + return provider.type === "none" + ? [] + : [{ providerType: "admin-openai", instance: provider }]; + } const rawChain = userRow?.aiProviderChain ?? null; const chain = parseProviderChain(rawChain).filter((e) => e.enabled); @@ -634,10 +640,8 @@ export function userRowHasProviderCredential( export async function hasAnyConfiguredProvider( userId: string, ): Promise { - const policy = providerCredentialPolicy( - providerWorkAuthorityForRecord(userId), - ); - if (policy === "deny") return false; + const authority = providerWorkAuthorityForRecord(userId); + if (providerCredentialPolicy(authority) === "deny") return false; const userRow = await prisma.user.findUnique({ where: { id: userId }, select: { @@ -654,9 +658,11 @@ export async function hasAnyConfiguredProvider( codexAccessTokenEncrypted: true, codexRefreshTokenEncrypted: true, useCentralCodex: true, + managedProfileAt: true, }, }); if (!userRow) return false; + const policy = providerCredentialPolicy(authority, userRow.managedProfileAt); const settings = await prisma.appSettings.findUnique({ where: { id: "singleton" }, select: { @@ -790,10 +796,10 @@ function resolveManagedByFromRow( export async function resolveProviderAvailability( userId: string, ): Promise<{ aiAvailable: boolean; managedBy: ProviderManagedBy | null }> { - const policy = providerCredentialPolicy( - providerWorkAuthorityForRecord(userId), - ); - if (policy === "deny") return { aiAvailable: false, managedBy: null }; + const authority = providerWorkAuthorityForRecord(userId); + if (providerCredentialPolicy(authority) === "deny") { + return { aiAvailable: false, managedBy: null }; + } const userRow = await prisma.user.findUnique({ where: { id: userId }, select: { @@ -810,9 +816,11 @@ export async function resolveProviderAvailability( codexAccessTokenEncrypted: true, codexRefreshTokenEncrypted: true, useCentralCodex: true, + managedProfileAt: true, }, }); if (!userRow) return { aiAvailable: false, managedBy: null }; + const policy = providerCredentialPolicy(authority, userRow.managedProfileAt); const settings = await prisma.appSettings.findUnique({ where: { id: "singleton" }, select: { diff --git a/src/lib/sharing/provider-work-authority.ts b/src/lib/sharing/provider-work-authority.ts index 273353403..c8584b9d5 100644 --- a/src/lib/sharing/provider-work-authority.ts +++ b/src/lib/sharing/provider-work-authority.ts @@ -27,10 +27,14 @@ const workerAuthority = new AsyncLocalStorage(); export function providerCredentialPolicy( authority: ProviderWorkAuthority | null, + managedProfileAt: Date | null = null, ): ProviderCredentialPolicy { if (authority === null) return "deny"; if (authority.origin === "delegate") return "deny"; if (authority.origin === "guardian") return "operator-default"; + if (authority.origin === "system" && managedProfileAt !== null) { + return "operator-default"; + } return "personal"; } diff --git a/tests/integration/sharing-provider-origin.test.ts b/tests/integration/sharing-provider-origin.test.ts index b08304ba2..e3e78fef3 100644 --- a/tests/integration/sharing-provider-origin.test.ts +++ b/tests/integration/sharing-provider-origin.test.ts @@ -57,7 +57,12 @@ import { } from "@/lib/sharing/provider-work-authority"; import { runInsightStatusGenerate } from "@/lib/jobs/insight-status-generate"; import type { InsightStatusGeneratePayload } from "@/lib/jobs/insight-status-generate"; -import { resolveProvider } from "@/lib/ai/provider"; +import { + hasAnyConfiguredProvider, + resolveProvider, + resolveProviderAvailability, + resolveProviderChain, +} from "@/lib/ai/provider"; import { invalidateStatusInsightsForTypes } from "@/lib/insights/status-invalidation"; import { warmOneNarrative } from "@/lib/jobs/period-narrative-warm"; @@ -357,4 +362,39 @@ describe("sharing provider origin", () => { force: true, }); }); + + it("keeps system work for a managed record off stale personal providers", async () => { + await getPrismaClient().user.create({ + data: { + id: "managed-system-record", + username: "managed-system-record", + email: "managed-system-record@example.test", + role: "USER", + managedProfileAt: new Date(), + aiProvider: "LOCAL", + aiBaseUrl: "https://stale-managed-provider.example.test/v1", + }, + }); + const system = authority({ + origin: "system", + recordUserId: "managed-system-record", + actorUserId: null, + grantId: null, + }); + + const [provider, chain, configured, availability] = + await withProviderWorkAuthority(system, async () => + Promise.all([ + resolveProvider("managed-system-record"), + resolveProviderChain("managed-system-record"), + hasAnyConfiguredProvider("managed-system-record"), + resolveProviderAvailability("managed-system-record"), + ]), + ); + + expect(provider.type).toBe("none"); + expect(chain).toEqual([]); + expect(configured).toBe(false); + expect(availability).toEqual({ aiAvailable: false, managedBy: null }); + }); });