test(evals): normalize disposable team fixture runtime

Preserve the shipped team graph and non-runtime sidecar fields while selecting the campaign OpenCode account through the catalog package. Verify the actual no-overrides installer without model execution.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-11-05 22:55:49 -06:00
1 parent c9e3418fef
commit 5dc8314e39
6 files changed
+136 -4

No files matched your search

@@ -149,4 +149,35 @@ describeEmbeddedPostgres("teams catalog install with no caller adapter overrides
.map((row) => row.adapterType);
expect(otherAdapters).toEqual(["paperclip_runner", "paperclip_runner"]);
});
it("derived catalog installs selected native runtime without caller overrides", async () => {
const repositoryRoot = path.resolve(import.meta.dirname, "../../..");
const temporaryRoot = await fs.mkdtemp(path.join(os.tmpdir(), "team-runtime-derived-pg-"));
const oldDir = process.env.PAPERCLIP_TEAMS_CATALOG_DIR, oldDefault = process.env.PAPERCLIP_TEAMS_CATALOG_DEFAULT_ADAPTER_TYPE;
try {
const helper = await import("../../../tests/runner-e2e/chat-catalog-domains/external-team-runtime-fixture.js");
Object.assign(process.env, await helper.teamRuntimeCatalogServerEnvironment({ repositoryRoot, temporaryRoot, executionIds: ["chat-features.runner-opencode.local.teams.install-team"] }));
const companyId = await seedEmptyCompany();
const { secretService } = await import("../services/secrets.js");
const account = await secretService(db).create(companyId, { name: "Derived package account", provider: "local_encrypted", value: "synthetic-model-free-account" });
const manifest = JSON.parse(await fs.readFile(path.join(repositoryRoot, "packages/teams-catalog/generated/catalog.json"), "utf8"));
const team = manifest.teams.find((row: {slug: string}) => row.slug === "product-engineering");
const config = { provider: "opencode", model: "openrouter/deepseek/deepseek-v4.1-flash-1017", timeoutSec: 0, lifecycleMode: "per_turn", opencodePermissionMode: "allow", env: { OPENROUTER_API_KEY: { type: "secret_ref", secretId: account.id, version: "latest" } } };
const derived = await helper.normalizeTeamRuntimeCatalog({ repositoryRoot, temporaryRoot, team, adapters: Object.fromEntries(team.agentSlugs.map((slug: string) => [slug, { adapterType: "paperclip_runner", adapterConfig: config }])) });
vi.resetModules();
const { teamsCatalogService: actualService, getCatalogTeamOrThrow } = await import("../services/teams-catalog.js");
expect((await getCatalogTeamOrThrow(team.id)).contentHash).toBe(derived.effectiveContentHash);
const svc = actualService(db);
const preview = await svc.previewCatalogTeamImport(companyId, team.id, {});
for (const member of preview.portabilityPreview.manifest.agents) expect(member.adapterConfig).toMatchObject(config);
await svc.installCatalogTeam(companyId, team.id, { collisionStrategy: "rename", include: { projects: false, issues: false } });
const saved = await db.select().from(agents).where(eq(agents.companyId, companyId));
expect(saved).toHaveLength(3);
for (const member of saved) { expect(member.adapterType).toBe("paperclip_runner"); expect(member.adapterConfig).toMatchObject(config); }
} finally {
if (oldDir === undefined) delete process.env.PAPERCLIP_TEAMS_CATALOG_DIR; else process.env.PAPERCLIP_TEAMS_CATALOG_DIR = oldDir;
if (oldDefault === undefined) delete process.env.PAPERCLIP_TEAMS_CATALOG_DEFAULT_ADAPTER_TYPE; else process.env.PAPERCLIP_TEAMS_CATALOG_DEFAULT_ADAPTER_TYPE = oldDefault;
await fs.rm(temporaryRoot, { recursive: true, force: true });
}
}, 30_000);
});
@@ -6024,6 +6024,9 @@
"sourceFiles": [
"chat-catalog-domains/external-team-install.ts",
"chat-catalog-domains/external-team-conditional.ts",
"chat-catalog-domains/external-team-runtime-fixture.ts",
"server.ts",
"../../packages/teams-catalog/src/catalog-builder.ts",
"../../server/src/services/teams-catalog.ts",
"../../server/src/services/company-skills.ts",
"../../server/src/services/company-portability.ts",
@@ -6034,7 +6037,7 @@
"chat-flow.ts",
"chat-scenarios/teams.yaml"
],
"oracleVersion": "paperclip.chat-catalog.team-install.v6",
"oracleVersion": "paperclip.chat-catalog.team-install.v7",
"expectedRunCount": 2,
"expectedHumanMessageCount": 2,
"turnTimeoutMs": 120000,
@@ -1,3 +1,5 @@
import path from "node:path";
import { normalizeTeamRuntimeCatalog } from "./external-team-runtime-fixture.js";
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";
@@ -269,7 +271,7 @@ export const teamInstallExternalCases = [
async (ctx) => {
const teams =
await ctx.input.api.get<CatalogTeam[]>("/api/teams/catalog");
const team = teams.find(
let team = teams.find(
(row) =>
row.slug === "product-engineering" &&
row.compatibility === "compatible",
@@ -300,6 +302,12 @@ export const teamInstallExternalCases = [
);
const selectedRuntime = selectedTeamInstallRuntime(ctx, actor);
const adapterOverrides = teamInstallOverrides(team, selectedRuntime);
const temporaryRoot = process.env.PAPERCLIP_RUNNER_E2E_TEMP_ROOT;
if (!temporaryRoot) throw new Error("Team runtime fixture requires its disposable attempt root");
const catalogRuntime = await normalizeTeamRuntimeCatalog({ temporaryRoot, repositoryRoot: path.resolve(import.meta.dirname, "../../.."), team, adapters: adapterOverrides });
team = catalogRuntime.team;
const served = await ctx.input.api.get<CatalogTeam[]>("/api/teams/catalog");
if (served.find(row => row.id === team.id)?.contentHash !== catalogRuntime.effectiveContentHash) throw new Error("Server did not serve the derived runtime catalog");
const conditional = await prepareTeamConditions(ctx, team);
const preview = conditional.conditionalPreview;
if (preview.errors.length || preview.portabilityPreview.errors.length)
@@ -314,7 +322,7 @@ 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}. 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)}.`,
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. This disposable catalog package already selects OpenCode/OpenRouter ${selectedRuntime.model}; its credential stays in the existing same-company secret reference.`,
});
const session = await ctx.input.api.get<{ user: { id: string } }>("/api/auth/get-session");
if (!session.user?.id) throw new Error("Team installer requires its actual responsible human session");
@@ -323,6 +331,7 @@ export const teamInstallExternalCases = [
humanId: session.user.id,
...conditional,
selectedRuntime,
catalogRuntime,
team,
before,
companyPrefix: ctx.input.fixtures.company.issuePrefix,
@@ -337,13 +346,16 @@ export const teamInstallExternalCases = [
gradeTeamInstall,
{
authentication: "human-session",
oracleVersion: "paperclip.chat-catalog.team-install.v6",
oracleVersion: "paperclip.chat-catalog.team-install.v7",
expectedRunCount: 2,
expectedHumanMessageCount: 2,
attemptTimeoutMs: 480_000,
sourceFiles: [
"chat-catalog-domains/external-team-install.ts",
"chat-catalog-domains/external-team-conditional.ts",
"chat-catalog-domains/external-team-runtime-fixture.ts",
"server.ts",
"../../packages/teams-catalog/src/catalog-builder.ts",
"../../server/src/services/teams-catalog.ts",
"../../server/src/services/company-skills.ts",
"../../server/src/services/company-portability.ts",
@@ -0,0 +1,27 @@
import { mkdtemp, readFile, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { expect, it } from "vitest";
import YAML from "yaml";
import { normalizeTeamRuntimeCatalog, teamRuntimeCatalogRoot, teamRuntimeCatalogServerEnvironment } from "./external-team-runtime-fixture.js";
const repositoryRoot = path.resolve(import.meta.dirname, "../../..");
it("serves a derived runtime-only catalog while preserving shipped roles, permissions, skills and other teams", async () => {
const temporaryRoot = await mkdtemp(path.join(os.tmpdir(), "team-runtime-fixture-"));
try {
expect(await teamRuntimeCatalogServerEnvironment({ executionIds: ["unrelated"], temporaryRoot, repositoryRoot })).toEqual({});
const env = await teamRuntimeCatalogServerEnvironment({ executionIds: ["chat-features.runner-opencode.local.teams.install-team"], temporaryRoot, repositoryRoot });
expect(env.PAPERCLIP_TEAMS_CATALOG_DEFAULT_ADAPTER_TYPE).toBe("paperclip_runner");
const original = JSON.parse(await readFile(path.join(repositoryRoot, "packages/teams-catalog/generated/catalog.json"), "utf8"));
const team = original.teams.find((row: { slug: string }) => row.slug === "product-engineering");
const adapterConfig = { provider: "opencode", model: "openrouter/deepseek/deepseek-v4.1-flash-1017", timeoutSec: 0, lifecycleMode: "per_turn", opencodePermissionMode: "allow", env: { OPENROUTER_API_KEY: { type: "secret_ref", secretId: "test-account", version: "latest" } } };
const adapters = Object.fromEntries(team.agentSlugs.map((slug: string) => [slug, { adapterType: "paperclip_runner", adapterConfig }]));
const result = await normalizeTeamRuntimeCatalog({ repositoryRoot, temporaryRoot, team, adapters });
expect(result.originalContentHash).toBe(team.contentHash);
expect(result.effectiveContentHash).not.toBe(team.contentHash);
const sidecar = YAML.parse(await readFile(path.join(teamRuntimeCatalogRoot(temporaryRoot), team.path, ".paperclip.yaml"), "utf8"));
expect(sidecar.agents.cto.permissions).toEqual({ canCreateAgents: true });
for (const slug of team.agentSlugs) expect(sidecar.agents[slug].adapter).toEqual({ type: "paperclip_runner", config: adapterConfig });
expect(result.team.requiredSkills).toEqual(team.requiredSkills);
await expect(normalizeTeamRuntimeCatalog({ repositoryRoot, temporaryRoot, team: { ...team, contentHash: "foreign" }, adapters })).rejects.toThrow("exact unmodified");
} finally { await rm(temporaryRoot, { recursive: true, force: true }); }
});
@@ -0,0 +1,56 @@
import { parseFrontmatterMarkdown } from "../../../packages/shared/src/frontmatter.js";
import YAML from "yaml";
import { cp, mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { isDeepStrictEqual } from "node:util";
import { buildCatalogManifest } from "../../../packages/teams-catalog/src/catalog-builder.js";
import type { CatalogTeam } from "../../../packages/shared/src/types/teams-catalog.js";
import { reviewedChatExecutionIds } from "../chat-runtime-profile.js";
const selectedIds = reviewedChatExecutionIds("teams.install-team");
export const teamRuntimeCatalogRoot = (temporaryRoot: string) => path.join(temporaryRoot, "team-runtime-catalog");
/** Only the disposable installer fixture uses this catalog and native default. */
export async function teamRuntimeCatalogServerEnvironment(input: { executionIds: string[]; temporaryRoot: string; repositoryRoot: string }) {
if (!input.executionIds.some(id => selectedIds.includes(id))) return {};
if (input.executionIds.length !== 1) throw new Error("Team runtime catalog requires its isolated execution");
const source = path.join(input.repositoryRoot, "packages/teams-catalog"), target = teamRuntimeCatalogRoot(input.temporaryRoot);
await mkdir(target, { recursive: true });
for (const entry of ["package.json", "catalog", "generated"]) await cp(path.join(source, entry), path.join(target, entry), { recursive: true, force: false, errorOnExist: true });
return { PAPERCLIP_TEAMS_CATALOG_DIR: target, PAPERCLIP_TEAMS_CATALOG_DEFAULT_ADAPTER_TYPE: "paperclip_runner" };
}
/** Add runtime/account metadata only; all shipped team files remain byte-identical. */
export async function normalizeTeamRuntimeCatalog(input: { temporaryRoot: string; repositoryRoot: string; team: CatalogTeam; adapters: Record<string, { adapterType: string; adapterConfig: Record<string, unknown> }> }) {
const packageDir = teamRuntimeCatalogRoot(input.temporaryRoot);
const original = JSON.parse(await readFile(path.join(input.repositoryRoot, "packages/teams-catalog/generated/catalog.json"), "utf8"));
const source = original.teams.find((team: CatalogTeam) => team.id === input.team.id) as CatalogTeam | undefined;
if (!source || source.contentHash !== input.team.contentHash || !isDeepStrictEqual(Object.keys(input.adapters).sort(), [...source.agentSlugs].sort())) throw new Error("Team runtime fixture must start from the exact unmodified accepted package");
for (const file of source.files) {
const shipped = await readFile(path.join(input.repositoryRoot, "packages/teams-catalog", source.path, file.path));
const derived = await readFile(path.join(packageDir, source.path, file.path));
if (!shipped.equals(derived)) throw new Error("Team runtime fixture changed a shipped non-runtime file");
}
const sidecarPath = path.join(packageDir, source.path, ".paperclip.yaml");
const originalSidecar = YAML.parse(await readFile(sidecarPath, "utf8"));
const sidecar = structuredClone(originalSidecar);
sidecar.agents ??= {};
for (const [slug, adapter] of Object.entries(input.adapters)) sidecar.agents[slug] = { ...sidecar.agents[slug], adapter: { type: adapter.adapterType, config: adapter.adapterConfig } };
const serialized = YAML.stringify(sidecar, { aliasDuplicateObjects: false, lineWidth: 0 });
if (!isDeepStrictEqual(parseFrontmatterMarkdown(`---\n${serialized}---\n`).frontmatter, sidecar)) throw new Error("Runtime sidecar is not supported by the actual portability parser");
await writeFile(sidecarPath, serialized, { mode: 0o600 });
const restored = structuredClone(sidecar);
for (const slug of Object.keys(input.adapters)) {
delete restored.agents[slug].adapter;
if (!Object.keys(restored.agents[slug]).length) delete restored.agents[slug];
}
if (!isDeepStrictEqual(restored, originalSidecar)) throw new Error("Team runtime fixture changed non-runtime sidecar fields");
const skills = JSON.parse(await readFile(path.join(input.repositoryRoot, "packages/skills-catalog/generated/catalog.json"), "utf8"));
const built = await buildCatalogManifest({ packageDir, generatedAt: original.generatedAt, catalogSkills: skills.skills });
if (built.errors.length) throw new Error(`Derived runtime catalog is invalid: ${built.errors.join("; ")}`);
const effective = built.manifest.teams.find(team => team.id === source.id)!;
const withoutRuntime = (team: CatalogTeam) => ({ ...team, contentHash: "", fileCount: 0, files: team.files.filter(file => file.path !== ".paperclip.yaml") });
if (!isDeepStrictEqual(withoutRuntime(effective), withoutRuntime(source)) || effective.contentHash === source.contentHash || built.manifest.teams.some(team => team.id !== source.id && !isDeepStrictEqual(team, original.teams.find((row: CatalogTeam) => row.id === team.id)))) throw new Error("Derived catalog changed non-runtime team metadata");
await writeFile(path.join(packageDir, "generated/catalog.json"), JSON.stringify(built.manifest, null, 2) + "\n");
return { originalContentHash: source.contentHash, effectiveContentHash: effective.contentHash, originalFilesUnchanged: true, team: effective };
}
+3
View File
@@ -1,3 +1,4 @@
import { teamRuntimeCatalogServerEnvironment } from "./chat-catalog-domains/external-team-runtime-fixture.js";
import { stopSetupHealthIfRequested } from "./chat-setup-health.js";
import { publishInvitationSourceProof, invitationSourceProofPath } from "./chat-catalog-domains/identity-invite-source-proof.js";
import { readSelectedVaultServerEnvironment } from "./chat-vault-private-server.js";
@@ -72,6 +73,7 @@ const publicOriginReceipt = process.env[PUBLIC_ORIGIN_RECEIPT_ENV] ? await readV
if (publicOriginReceipt && process.env.PAPERCLIP_RUNNER_E2E_PUBLIC_MCP !== "1") throw new Error("Owned public origin requires the exact authenticated selected case");
for (const key of [PUBLIC_ORIGIN_RECEIPT_ENV, PUBLIC_ORIGIN_CONTROL_ENV, "PAPERCLIP_PUBLIC_URL", "PAPERCLIP_AUTH_PUBLIC_BASE_URL", "PAPERCLIP_MANAGED_RUNTIME_PUBLIC_URL", "PAPERCLIP_ALLOWED_HOSTNAMES"]) delete archiveEnvironment.source[key];
const vaultEnvironment = await readSelectedVaultServerEnvironment({ executionIds, temporaryRoot, backendOrigin: `http://127.0.0.1:${port}`, env: process.env });
const teamCatalogEnvironment = await teamRuntimeCatalogServerEnvironment({ executionIds, temporaryRoot, repositoryRoot });
const serverEnvironment = buildPaperclipServerEnvironment(installedRelease
? installedReleaseEnvironment(archiveEnvironment.source, repositoryRoot, path.join(temporaryRoot, "provider-bin"))
: archiveEnvironment.source, {
@@ -117,6 +119,7 @@ const serverEnvironment = buildPaperclipServerEnvironment(installedRelease
// to Playwright in this harness, so never create a developer desktop tab.
PAPERCLIP_OPEN_ON_LISTEN: "false",
...vaultEnvironment,
...teamCatalogEnvironment,
});
assertIsolatedServerEnvironment(serverEnvironment, {
temporaryRoot,