mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
fix: keep managed system work operator-scoped
Resolve managed record credentials through the operator policy for system work.
This commit is contained in:
1 parent
2b2080f48c
commit
adb932052c
3 files changed
+76
-24
No files matched your search
+31
-23
@@ -405,11 +405,8 @@ async function resolveAdminCodexProvider(): Promise<AIProvider | null> {
|
||||
* 4. Nothing configured → NoProvider().
|
||||
*/
|
||||
export async function resolveProvider(userId: string): Promise<AIProvider> {
|
||||
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<AIProvider> {
|
||||
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<AIProvider> {
|
||||
export async function resolveProviderChain(
|
||||
userId: string,
|
||||
): Promise<ProviderChainResolved[]> {
|
||||
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<boolean> {
|
||||
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: {
|
||||
|
||||
@@ -27,10 +27,14 @@ const workerAuthority = new AsyncLocalStorage<ProviderWorkAuthority>();
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user