mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:28:46 +01:00
fix(evals): preserve assignment permission in team fixture request
Use the complete public permissions schema and retain the independently observed same-company CEO assignment capability. Reject foreign or unverified actor metadata before writing, and verify the effective capability remains unchanged. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
78d91b0e2f
commit
6e5a430118
2 files changed
+26
-5
No files matched your search
@@ -1,8 +1,9 @@
|
||||
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
|
||||
import type { Agent } from "../../../packages/shared/src/types/agent.js";
|
||||
import { CHAT_OPENCODE_MODEL } from "../chat-runtime-profile.js";
|
||||
import { updateAgentPermissionsSchema } from "../../../packages/shared/src/validators/agent.js";
|
||||
import { catalogTeamInstallSchema } from "../../../packages/shared/src/validators/teams-catalog.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { teamInstallExternalCases, selectedTeamInstallRuntime, teamInstallOverrides, verifyInstalledTeamRuntime } from "./external-team-install.js";
|
||||
function valid() {
|
||||
const team = {
|
||||
@@ -212,3 +213,20 @@ it("real resource counts and actor attribution cannot conceal wrong member provi
|
||||
(o: ReturnType<typeof valid>) => { o.state.selectedRuntime.model = "openrouter/other/model"; o.state.after.agents.forEach(agent => { agent.adapterConfig.model = "openrouter/other/model"; }); },
|
||||
]) { const o = valid(); change(o); expect(teamInstallExternalCases[0]!.grade(o).passed).toBe(false); }
|
||||
});
|
||||
|
||||
it.each([true,false])("sends the complete permission schema without changing effective assignment=%s",async canAssignTasks=>{
|
||||
const actor={id:"ceo",companyId:"company",role:"ceo",access:{canAssignTasks}};
|
||||
const patch=vi.fn(async(_route:string,body:unknown)=>{expect(updateAgentPermissionsSchema.parse(body)).toEqual({canCreateAgents:true,canAssignTasks});throw new Error("stop-after-validated-permission-request");});
|
||||
const ctx={input:{fixtures:{company:{id:"company"},agent:{id:"ceo"}},api:{get:async(route:string)=>route==="/api/teams/catalog"?[{slug:"product-engineering",compatibility:"compatible"}]:actor,patch}}} as unknown as ChatCatalogContext;
|
||||
await expect(teamInstallExternalCases[0]!.setup(ctx)).rejects.toThrow("stop-after-validated-permission-request");
|
||||
expect(patch).toHaveBeenCalledOnce();
|
||||
});
|
||||
it.each(["foreign company","wrong agent","not CEO","missing effective assignment"])("refuses %s identity before permission mutation",async invalid=>{
|
||||
const actor={id:"ceo",companyId:"company",role:"ceo",access:{canAssignTasks:true}};
|
||||
if(invalid==="foreign company")actor.companyId="foreign";
|
||||
if(invalid==="wrong agent")actor.id="other";
|
||||
if(invalid==="not CEO")actor.role="engineer";
|
||||
if(invalid==="missing effective assignment")delete(actor as {access?:unknown}).access;
|
||||
const patch=vi.fn(); const ctx={input:{fixtures:{company:{id:"company"},agent:{id:"ceo"}},api:{get:async(route:string)=>route==="/api/teams/catalog"?[{slug:"product-engineering",compatibility:"compatible"}]:actor,patch}}} as unknown as ChatCatalogContext;
|
||||
await expect(teamInstallExternalCases[0]!.setup(ctx)).rejects.toThrow("observed installer");expect(patch).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
gradeTeamConditions,
|
||||
} from "./external-team-conditional.js";
|
||||
import { expect } from "@playwright/test";
|
||||
import type { Agent } from "../../../packages/shared/src/types/agent.js";
|
||||
import type { Agent, AgentDetail } from "../../../packages/shared/src/types/agent.js";
|
||||
import type {
|
||||
CatalogTeam,
|
||||
CatalogTeamImportPreviewResult,
|
||||
@@ -277,19 +277,22 @@ export const teamInstallExternalCases = [
|
||||
throw new Error(
|
||||
"Setup blocker: real compatible Product Engineering team catalog package is unavailable",
|
||||
);
|
||||
const beforePermissions = await ctx.input.api.get<AgentDetail>(`/api/agents/${ctx.input.fixtures.agent.id}`);
|
||||
if (beforePermissions.id !== ctx.input.fixtures.agent.id || beforePermissions.companyId !== ctx.input.fixtures.company.id || beforePermissions.role !== "ceo" || typeof beforePermissions.access?.canAssignTasks !== "boolean") throw new Error("Team fixture requires the independently observed installer identity and assignment authority");
|
||||
await ctx.input.api.patch(
|
||||
`/api/agents/${ctx.input.fixtures.agent.id}/permissions`,
|
||||
{ canCreateAgents: true },
|
||||
{ canCreateAgents: true, canAssignTasks: beforePermissions.access.canAssignTasks },
|
||||
);
|
||||
await ctx.input.api.patch(root(ctx), {
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
});
|
||||
const actor = await ctx.input.api.get<Agent>(
|
||||
const actor = await ctx.input.api.get<AgentDetail>(
|
||||
`/api/agents/${ctx.input.fixtures.agent.id}`,
|
||||
);
|
||||
if (
|
||||
actor.lifecycleState !== "ready" ||
|
||||
actor.permissions?.canCreateAgents !== true
|
||||
actor.permissions?.canCreateAgents !== true ||
|
||||
actor.access?.canAssignTasks !== beforePermissions.access.canAssignTasks
|
||||
)
|
||||
throw new Error(
|
||||
"Setup blocker: actual selected installer actor lacks a ready account or creation authority",
|
||||
|
||||
Reference in new issue
Block a user