evOo-local/packages/db/src/computers.ts
Cursor Agent 8ae36763a7 fix(api): scope Team Computer bot homes per member in shared Spaces
Team mode shares one disk per Space. RPC and executor paths under
bots/<otherBotId>/ were reachable with any member-owned entry bot,
leaking another member's workspace files while same-user cross-bot
access stays intentional.

Add path guards, filter bot listings, regression tests.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 06:20:49 +00:00

74 lines
2.1 KiB
TypeScript

import type { ComputerMode } from "@rakazo/contracts";
import type { Prisma, PrismaClient } from "./client.js";
export type { ComputerMode } from "@rakazo/contracts";
export function parseComputerMode(scope: string): ComputerMode {
if (scope === "team" || scope === "dedicated") return scope;
throw new Error(`Unknown computer scope: ${scope}`);
}
export function computerScopeKey(mode: ComputerMode, spaceId: string, botId?: string) {
if (mode === "team") return `team:${spaceId}`;
if (!botId) throw new Error("Dedicated computers require a bot id");
return `bot:${botId}`;
}
export function computerHomeKey(mode: ComputerMode, spaceId: string, botId?: string) {
if (mode === "team") return `team-${spaceId}`;
if (!botId) throw new Error("Dedicated computers require a bot id");
return botId;
}
type ComputerDb = Pick<PrismaClient, "computer">;
type BotDb = Pick<PrismaClient, "bot">;
type ExecutionLeaseDb = Pick<PrismaClient, "computerExecutionLease">;
export async function listSpaceUserBotIds(
prisma: BotDb,
spaceId: string,
userId: string,
): Promise<Set<string>> {
const rows = await prisma.bot.findMany({
where: { spaceId, userId, archivedAt: null },
select: { id: true },
});
return new Set(rows.map((row) => row.id));
}
/** Expire leases as fencing tombstones so the next acquire increments fence. */
export async function expireComputerExecutionLeases(
prisma: ExecutionLeaseDb,
where: Prisma.ComputerExecutionLeaseWhereInput,
): Promise<void> {
await prisma.computerExecutionLease.updateMany({
where,
data: { expiresAt: new Date(0) },
});
}
export async function ensureComputerRecord(
prisma: ComputerDb,
input: {
mode: ComputerMode;
spaceId: string;
userId: string;
botId?: string;
kind: string;
},
) {
const scopeKey = computerScopeKey(input.mode, input.spaceId, input.botId);
return prisma.computer.upsert({
where: { scopeKey },
create: {
spaceId: input.spaceId,
userId: input.userId,
scope: input.mode,
scopeKey,
homeKey: computerHomeKey(input.mode, input.spaceId, input.botId),
kind: input.kind,
},
update: {},
});
}