Merge pull request #65 from cynarAI/cursor/teaching-takeover-clobber-5453
fix(api): block takeover while teaching on sibling Team Computer bot
This commit is contained in:
commit
fbf9a6cd03
@ -172,7 +172,11 @@ import {
|
||||
readServerUpdateStatus,
|
||||
UpdaterProxyError,
|
||||
} from "./server-update.js";
|
||||
import { assertTeachingSendAllowed, createTaughtSkillsService } from "./taught-skills.js";
|
||||
import {
|
||||
assertTeachingSendAllowed,
|
||||
assertTeachingTakeoverAllowed,
|
||||
createTaughtSkillsService,
|
||||
} from "./taught-skills.js";
|
||||
import {
|
||||
isPeerRun,
|
||||
loadAllMessages,
|
||||
@ -1809,6 +1813,12 @@ export function createRouter(deps: RouterDeps) {
|
||||
if (!bot.computer?.providerRef || bot.computer.state !== "running") {
|
||||
throw new ORPCError("BAD_REQUEST", { message: "computer must be running" });
|
||||
}
|
||||
await assertTeachingTakeoverAllowed(
|
||||
deps.prisma,
|
||||
context.actor,
|
||||
input.botId,
|
||||
bot.computer.id,
|
||||
);
|
||||
if (hasActiveComputerControl(bot.computer) && bot.computer.controlBotId === bot.id) {
|
||||
await bindWaitingTakeoverToControl(deps, {
|
||||
spaceId: context.actor.spaceId,
|
||||
|
||||
@ -110,6 +110,27 @@ export async function assertTeachingSendAllowed(
|
||||
}
|
||||
}
|
||||
|
||||
export async function assertTeachingTakeoverAllowed(
|
||||
prisma: PrismaClient,
|
||||
actor: Actor,
|
||||
botId: string,
|
||||
computerId: string,
|
||||
): Promise<void> {
|
||||
const teachingElsewhere = await prisma.taughtSkill.findFirst({
|
||||
where: {
|
||||
spaceId: actor.spaceId,
|
||||
userId: actor.userId,
|
||||
status: "recording",
|
||||
botId: { not: botId },
|
||||
bot: { computerId },
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
if (teachingElsewhere) {
|
||||
throw new ORPCError("CONFLICT", { message: "Stop teaching first" });
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelActiveRuns(
|
||||
deps: TaughtSkillsDeps,
|
||||
_actor: Actor,
|
||||
|
||||
@ -694,6 +694,46 @@ describeWithDatabase("API authorization and resource isolation", () => {
|
||||
expect(ownerCrossBot.content).toBe(secret);
|
||||
});
|
||||
|
||||
it("blocks Team Computer takeover on another bot while teaching is recording", async () => {
|
||||
const cookie = await signup(app, `teach-takeover-${stamp}@rakazo.test`, "Teach Takeover");
|
||||
const teacher = await rpc<Bot>(app, cookie, "bots/create", botInput("Teacher"));
|
||||
const sibling = await rpc<Bot>(app, cookie, "bots/create", botInput("Sibling"));
|
||||
const [teacherRow, siblingRow] = await Promise.all([
|
||||
handles.prisma.bot.findUniqueOrThrow({ where: { id: teacher.id } }),
|
||||
handles.prisma.bot.findUniqueOrThrow({ where: { id: sibling.id } }),
|
||||
]);
|
||||
expect(teacherRow.computerId).toBe(siblingRow.computerId);
|
||||
|
||||
await rpc(app, cookie, "computer/boot", { botId: teacher.id });
|
||||
const skill = await rpc<{ id: string; status: string }>(app, cookie, "skills/start", {
|
||||
botId: teacher.id,
|
||||
goal: "Demonstrate export",
|
||||
});
|
||||
expect(skill.status).toBe("recording");
|
||||
|
||||
const blockedTakeover = await raw(app, cookie, "computer/takeover", { botId: sibling.id });
|
||||
expect(blockedTakeover.status).toBe(409);
|
||||
expect(await blockedTakeover.text()).toMatch(/Stop teaching first/i);
|
||||
|
||||
await rpc(app, cookie, "computer/input", {
|
||||
botId: teacher.id,
|
||||
kind: "key",
|
||||
payload: { key: "x" },
|
||||
});
|
||||
|
||||
const stopped = await rpc<{ status: string }>(app, cookie, "skills/stop", {
|
||||
skillId: skill.id,
|
||||
});
|
||||
expect(stopped.status).toBe("draft");
|
||||
|
||||
const computer = await handles.prisma.computer.findUniqueOrThrow({
|
||||
where: { id: teacherRow.computerId! },
|
||||
select: { controlHolder: true, controlBotId: true },
|
||||
});
|
||||
expect(computer.controlHolder).toBe("bot");
|
||||
expect(computer.controlBotId).toBeNull();
|
||||
});
|
||||
|
||||
it("scopes Team Computer export to the exporting member's bot workspace in a shared Space", async () => {
|
||||
const owner = await signup(app, `export-owner-${stamp}@rakazo.test`, "Export Owner");
|
||||
const member = await signup(app, `export-member-${stamp}@rakazo.test`, "Export Member");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user