diff --git a/tests/runner-e2e/chat-catalog-domains/external-team-install.test.ts b/tests/runner-e2e/chat-catalog-domains/external-team-install.test.ts index cda3856eed9..073657e31c1 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-team-install.test.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-team-install.test.ts @@ -1,5 +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 { catalogTeamInstallSchema } from "../../../packages/shared/src/validators/teams-catalog.js"; import { describe, expect, it } from "vitest"; -import { teamInstallExternalCases } from "./external-team-install.js"; +import { teamInstallExternalCases, selectedTeamInstallRuntime, teamInstallOverrides, verifyInstalledTeamRuntime } from "./external-team-install.js"; function valid() { const team = { id: "team", @@ -10,6 +14,8 @@ function valid() { const agent = (id: string) => ({ id, companyId: "company", + adapterType: "paperclip_runner", + adapterConfig: { provider: "opencode", model: CHAT_OPENCODE_MODEL, timeoutSec: 0, env: { OPENROUTER_API_KEY: { type: "secret_ref", secretId: "account", version: "latest" } } }, urlKey: id, metadata: { paperclip: { catalogTeam: { catalogId: "team", originHash: "hash" } }, @@ -96,6 +102,8 @@ function valid() { localSkillStillOccupiesSlug: true, }, team, + selectedRuntime: { profileId: "runner-opencode", adapterType: "paperclip_runner", provider: "opencode", model: CHAT_OPENCODE_MODEL, accountRef: { type: "secret_ref", secretId: "account", version: "latest" } }, + runtimeReadback: { memberIds: ["coder", "cto"], provider: "opencode", model: CHAT_OPENCODE_MODEL, verifiedBeforeImportedWork: true }, before, first: structuredClone(after), after, @@ -152,3 +160,55 @@ describe("real catalog installer oracle", () => { ); }); }); + +function runtimeFixture() { + const o = valid(); + const ctx = { input: { execution: { profile: { id: "runner-opencode", provider: "opencode", model: CHAT_OPENCODE_MODEL, credential: "OPENROUTER_API_KEY" } }, fixtures: { company: { id: "company" }, secretRefs: { OPENROUTER_API_KEY: { type: "secret_ref", secretId: "account", version: "latest" } } } } } as unknown as ChatCatalogContext; + const actor = { ...o.state.after.agents[0]!, id: "ceo", adapterConfig: { ...o.state.after.agents[0]!.adapterConfig, cwd: "/not-a-team-cwd", instructionsFilePath: "/not-a-team-instruction", env: { ...o.state.after.agents[0]!.adapterConfig.env, UNRELATED_VALUE: "must-not-copy" } } } as unknown as Agent; + return { o, ctx, actor }; +} +it("the actual public per-slug override schema accepts only the selected runtime/account configuration, not CEO instructions or cwd", () => { + const f = runtimeFixture(), runtime = selectedTeamInstallRuntime(f.ctx, f.actor), overrides = teamInstallOverrides(f.o.state.team as never, runtime); + const payload = catalogTeamInstallSchema.parse({ targetManagerAgentId: "ceo", adapterOverrides: overrides }); + expect(Object.keys(payload.adapterOverrides!)).toEqual(["cto", "coder"]); + for (const entry of Object.values(payload.adapterOverrides!)) { + expect(entry).toEqual({ adapterType: "paperclip_runner", adapterConfig: { provider: "opencode", model: CHAT_OPENCODE_MODEL, timeoutSec: 0, lifecycleMode: "per_turn", opencodePermissionMode: "allow", env: { OPENROUTER_API_KEY: { type: "secret_ref", secretId: "account", version: "latest" } } } }); + } + expect(JSON.stringify(payload)).not.toContain("must-not-copy"); expect(JSON.stringify(payload)).not.toContain("not-a-team"); +}); +it.each(["provider", "model", "account", "plaintext", "company"])("rejects a wrong selected %s before exposing installer context", invalid => { + const f = runtimeFixture(); + if (invalid === "provider") f.actor.adapterConfig.provider = "codex"; + if (invalid === "model") f.actor.adapterConfig.model = "openrouter/other/model"; + if (invalid === "account") (f.actor.adapterConfig.env as Record).OPENROUTER_API_KEY = { type: "secret_ref", secretId: "foreign", version: "latest" }; + if (invalid === "plaintext") (f.actor.adapterConfig.env as Record).OPENROUTER_API_KEY = "must-not-copy"; + if (invalid === "company") f.actor.companyId = "foreign"; + expect(() => selectedTeamInstallRuntime(f.ctx, f.actor)).toThrow("actual configured"); +}); +it("independently reads every actual member before another human request or imported native work", async () => { + const f = runtimeFixture(), seen: string[] = []; + Object.assign(f.ctx.input, { api: { get: async (route: string) => { seen.push(route); return f.o.state.after.agents.find(agent => route.endsWith(agent.id)); } } }); + Object.assign(f.ctx, { allRuns: async () => [{ id: "source", agentId: "ceo", status: "succeeded", startedAt: "2026-10-28T01:00:00Z" }] }); + const proof = await verifyInstalledTeamRuntime(f.ctx, f.o.state.team as never, f.o.state.first as never, selectedTeamInstallRuntime(f.ctx, f.actor)); + expect(seen).toEqual(["/api/agents/cto", "/api/agents/coder"]); expect(proof).toMatchObject({ memberIds: ["coder", "cto"], verifiedBeforeImportedWork: true, model: CHAT_OPENCODE_MODEL }); +}); +it.each(["model", "account", "wrong returned identity", "already started"])("fails actual member runtime readback for %s", async invalid => { + const f = runtimeFixture(); + const saved = structuredClone(f.o.state.after.agents); + if (invalid === "model") saved[0]!.adapterConfig.model = "openrouter/other/model"; + if (invalid === "account") saved[0]!.adapterConfig.env.OPENROUTER_API_KEY.secretId = "foreign"; + if (invalid === "wrong returned identity") saved[0]!.id = "other"; + Object.assign(f.ctx.input, { api: { get: async (route: string) => saved[route.endsWith("cto") ? 0 : 1] } }); + Object.assign(f.ctx, { allRuns: async () => invalid === "already started" ? [{ id: "work", agentId: "cto", status: "running", startedAt: "2026-10-28T01:00:00Z" }] : [] }); + await expect(verifyInstalledTeamRuntime(f.ctx, f.o.state.team as never, f.o.state.first as never, selectedTeamInstallRuntime(f.ctx, f.actor))).rejects.toThrow(); +}); +it("real resource counts and actor attribution cannot conceal wrong member providers/models/accounts or missing pre-work readback", () => { + for (const change of [ + (o: ReturnType) => { o.state.after.agents[0]!.adapterConfig.provider = "codex"; }, + (o: ReturnType) => { o.state.first.agents[0]!.adapterConfig.model = "openrouter/other/model"; }, + (o: ReturnType) => { o.state.after.agents[0]!.adapterConfig.env.OPENROUTER_API_KEY.secretId = "foreign"; }, + (o: ReturnType) => { o.state.runtimeReadback.verifiedBeforeImportedWork = false; }, + (o: ReturnType) => { o.state.runtimeReadback.memberIds = ["other", "cto"]; }, + (o: ReturnType) => { 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); } +}); diff --git a/tests/runner-e2e/chat-catalog-domains/external-team-install.ts b/tests/runner-e2e/chat-catalog-domains/external-team-install.ts index d2edcf6b3c1..be46cfd27c5 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-team-install.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-team-install.ts @@ -1,3 +1,6 @@ +import type { ChatCatalogContext } from "../chat-catalog-contract.js"; +import { CHAT_OPENCODE_MODEL, CHAT_OPENCODE_PROFILE } from "../chat-runtime-profile.js"; +import type { SecretReference } from "../types.js"; import { prepareTeamConditions, observeTeamConditions, @@ -54,6 +57,39 @@ export function catalogTeamMembers( (agent) => catalogTeamProvenance(agent)?.catalogId === teamId, ); } +interface TeamInstallRuntime { + profileId: string; adapterType: "paperclip_runner"; provider: "opencode"; model: string; accountRef: SecretReference; +} +export function selectedTeamInstallRuntime(ctx: ChatCatalogContext, actor: Agent) { + const profile = ctx.input.execution.profile, config = actor.adapterConfig; + const ref = ctx.input.fixtures.secretRefs.OPENROUTER_API_KEY; + const bound = (config.env as Record | undefined)?.OPENROUTER_API_KEY as SecretReference | undefined; + if (profile.id !== CHAT_OPENCODE_PROFILE || profile.provider !== "opencode" || profile.model !== CHAT_OPENCODE_MODEL || profile.credential !== "OPENROUTER_API_KEY" + || actor.companyId !== ctx.input.fixtures.company.id || actor.adapterType !== "paperclip_runner" || config.provider !== "opencode" || config.model !== CHAT_OPENCODE_MODEL || config.timeoutSec !== 0 + || !ref || ref.type !== "secret_ref" || !ref.secretId || ref.version !== "latest" || Object.keys(ref).sort().join() !== "secretId,type,version" + || bound?.type !== ref.type || bound.secretId !== ref.secretId || bound.version !== ref.version) + throw new Error("Selected team installer must use its actual configured OpenCode OpenRouter1017 account"); + return { profileId: profile.id, adapterType: "paperclip_runner", provider: "opencode", model: profile.model, accountRef: { type: ref.type, secretId: ref.secretId, version: ref.version } } as TeamInstallRuntime; +} +export function teamInstallOverrides(team: Pick, runtime: TeamInstallRuntime) { + return Object.fromEntries(team.agentSlugs.map(slug => [slug, { adapterType: runtime.adapterType, adapterConfig: { provider: runtime.provider, model: runtime.model, timeoutSec: 0, lifecycleMode: "per_turn", opencodePermissionMode: "allow", env: { OPENROUTER_API_KEY: runtime.accountRef } } }])); +} +export function teamMemberRuntimeMatches(agent: Agent, expected: TeamInstallRuntime, companyId: string) { + const config = agent.adapterConfig, ref = (config?.env as Record | undefined)?.OPENROUTER_API_KEY as SecretReference | undefined; + return expected?.profileId === CHAT_OPENCODE_PROFILE && expected.adapterType === "paperclip_runner" && expected.provider === "opencode" && expected.model === CHAT_OPENCODE_MODEL + && expected.accountRef?.type === "secret_ref" && !!expected.accountRef.secretId && expected.accountRef.version === "latest" + && agent.companyId === companyId && agent.adapterType === expected.adapterType && config?.provider === expected.provider && config.model === expected.model && config.timeoutSec === 0 + && ref?.type === "secret_ref" && ref.secretId === expected.accountRef.secretId && ref.version === "latest"; +} +export async function verifyInstalledTeamRuntime(ctx: ChatCatalogContext, team: CatalogTeam, first: TeamResourceSnapshot, runtime: TeamInstallRuntime) { + const members = catalogTeamMembers(first, team.id); + if (members.length !== team.agentSlugs.length) throw new Error("Installed team runtime readback requires the complete actual package"); + const saved = await Promise.all(members.map(member => ctx.input.api.get(`/api/agents/${member.id}`))); + if (JSON.stringify(ids(saved)) !== JSON.stringify(ids(members)) || saved.some(member => !teamMemberRuntimeMatches(member, runtime, ctx.input.fixtures.company.id))) throw new Error("Actual saved installed-member runtime differs from selected OpenCode1017"); + const memberIds = new Set(members.map(member => member.id)); + if ((await ctx.allRuns()).some(run => memberIds.has(run.agentId) && !!run.startedAt)) throw new Error("Imported member work started before independent runtime readback"); + return { memberIds: saved.map(member => member.id).sort(), provider: runtime.provider, model: runtime.model, verifiedBeforeImportedWork: true }; +} const ids = (rows: Array<{ id: string }>) => rows.map((row) => row.id).sort(); function hasResourceLink( links: string[], @@ -131,6 +167,12 @@ export function gradeTeamInstall(value: unknown) { after.agents.length - before.agents.length === team.counts.agents, "Exactly the package's agents exist in this company with immutable catalog provenance; no unrelated or duplicate hires satisfy the count.", ); + const runtime = o.state.selectedRuntime as TeamInstallRuntime, runtimeReadback = o.state.runtimeReadback as { memberIds?: string[]; provider?: string; model?: string; verifiedBeforeImportedWork?: boolean } | undefined; + c.add("selected-runtime-preserved-for-every-installed-member", !!runtime && !!first && !!after && !!team + && runtimeReadback?.verifiedBeforeImportedWork === true && runtimeReadback.provider === runtime.provider && runtimeReadback.model === runtime.model && JSON.stringify(runtimeReadback.memberIds) === JSON.stringify(ids(members)) + && catalogTeamMembers(first, team.id).length === team.agentSlugs.length && catalogTeamMembers(first, team.id).every(member => teamMemberRuntimeMatches(member, runtime, o.companyId)) + && members.every(member => teamMemberRuntimeMatches(member, runtime, o.companyId)), + "Every actual imported member retains the selected OpenCode/OpenRouter1017 and exact existing company account reference; public saved configuration is independently read before any imported work."); c.add( "intended-project-and-routine-resources", !!team && @@ -252,6 +294,8 @@ export const teamInstallExternalCases = [ throw new Error( "Setup blocker: actual selected installer actor lacks a ready account or creation authority", ); + const selectedRuntime = selectedTeamInstallRuntime(ctx, actor); + const adapterOverrides = teamInstallOverrides(team, selectedRuntime); const conditional = await prepareTeamConditions(ctx, team); const preview = conditional.conditionalPreview; if (preview.errors.length || preview.portabilityPreview.errors.length) @@ -266,10 +310,11 @@ export const teamInstallExternalCases = [ await ctx.input.api.post(`${root(ctx)}/chats/${actor.id}`); await ctx.refreshIssue(); await ctx.input.api.patch(`/api/issues/${ctx.issue()!.id}`, { - description: `The accepted website team preview is ${team.name}, catalog key ${team.key}, content hash ${team.contentHash}. Install the complete selected package under the current CEO using this company's existing eligible runtime account. No team resource is installed yet. The selected runtime uses ${actor.adapterType} in environment ${ctx.input.fixtures.environment.id}.`, + description: `The accepted website team preview is ${team.name}, catalog key ${team.key}, content hash ${team.contentHash}. Install the complete selected package under the current CEO using this company's existing eligible runtime account. No team resource is installed yet. The selected runtime uses ${actor.adapterType} in environment ${ctx.input.fixtures.environment.id}. The existing eligible runtime is OpenCode/OpenRouter ${selectedRuntime.model}; its credential stays in the existing same-company secret reference. The supported installer takes these per-slug adapterOverrides for this accepted runtime selection: ${JSON.stringify(adapterOverrides)}.`, }); return { ...conditional, + selectedRuntime, team, before, companyPrefix: ctx.input.fixtures.company.issuePrefix, @@ -303,6 +348,7 @@ export const teamInstallExternalCases = [ async act(ctx, state) { await waitForReply(ctx); state.first = await observeTeamResources(ctx); + state.runtimeReadback = await verifyInstalledTeamRuntime(ctx, state.team as CatalogTeam, state.first as TeamResourceSnapshot, state.selectedRuntime as TeamInstallRuntime); state.initialRunId = (await ctx.allRuns())[0]?.id; state.firstConditional = await observeTeamConditions(ctx, state); const firstObservation = await observeExternal(ctx, state);