diff --git a/doc/sandbox-work-folders.md b/doc/sandbox-work-folders.md index 42784123fe..beedf9b42d 100644 --- a/doc/sandbox-work-folders.md +++ b/doc/sandbox-work-folders.md @@ -5,6 +5,8 @@ Set `PAPERCLIP_DEPLOYED_STACK_MANIFEST` to a JSON manifest matching `tests/runner-e2e/deployed-stack.ts`, `PAPERCLIP_DEPLOYED_STACK_AUTH` to a private 0600 JSON file containing `baseURL` and a normally authorized `boardApiToken`, and `PAPERCLIP_DEPLOYED_STACK_EVIDENCE` to an absolute output directory. +The token must belong to the manifest's `userId` and have company access. The +harness verifies that identity before file operations or paid agent execution. The harness never launches a local server. It checks the deployed commit and adapter inventory, exercises scoped file APIs, and starts real sandbox tasks for every configured profile, including cold/warm Git state and real timed saves. diff --git a/tests/runner-e2e/deployed-work-folders.spec.ts b/tests/runner-e2e/deployed-work-folders.spec.ts index 08aa6f0c6a..432a412d65 100644 --- a/tests/runner-e2e/deployed-work-folders.spec.ts +++ b/tests/runner-e2e/deployed-work-folders.spec.ts @@ -14,6 +14,9 @@ const folder = (scope: string, ownerId: string) => `/api/companies/${stack.compa test.beforeAll(async () => { const health = await api.json<{ commit: string }>("/api/health"); expect(health.commit, "Only exercise the declared deployed candidate").toBe(stack.commit); + const identity = await api.json<{ userId: string; companyIds: string[] }>("/api/cli-auth/me"); + expect(identity.userId, "The board token must belong to the manifest's responsible user").toBe(stack.userId); + expect(identity.companyIds, "The board token must have access to the acceptance company").toContain(stack.companyId); }); test("deployed candidate and complete supported adapter inventory", async ({}, info) => { @@ -95,7 +98,8 @@ for (const profile of stack.profiles) { expect(content.status).toBe(200); expect(await content.text()).toBe(nonce); const coldRunIds = new Set(cold.saves.map((save) => save.runId)); const coldSave = cold.saves.find((save) => !save.active && save.state === "saved")!; - const coldRun = await api.json<{ contextSnapshot: { paperclipWorkFolders: SandboxWorkFolderManifest } }>(`/api/heartbeat-runs/${coldSave.runId}`); + const coldRun = await api.json<{ status: string; contextSnapshot: { paperclipWorkFolders: SandboxWorkFolderManifest } }>(`/api/heartbeat-runs/${coldSave.runId}`); + expect(coldRun.status, "A saved checkpoint must not hide a failed adapter run").toBe("succeeded"); const coldManifest = coldRun.contextSnapshot.paperclipWorkFolders; expect(coldManifest.sandboxKey).toBeTruthy(); const owners = {task: issue.id, agent: profile.agentId, user: stack.userId, project: stack.projectId}; @@ -121,7 +125,8 @@ for (const profile of stack.profiles) { const warmContent = await api.request(`${base}/content?path=warm.txt`); expect(warmContent.status).toBe(200); expect(await warmContent.text()).toBe(nonce); const warmSave = warm.saves.find((save) => !save.active && save.state === "saved")!; - const warmRun = await api.json<{ contextSnapshot: { paperclipWorkFolders: SandboxWorkFolderManifest } }>(`/api/heartbeat-runs/${warmSave.runId}`); + const warmRun = await api.json<{ status: string; contextSnapshot: { paperclipWorkFolders: SandboxWorkFolderManifest } }>(`/api/heartbeat-runs/${warmSave.runId}`); + expect(warmRun.status, "The warm adapter run must also succeed").toBe("succeeded"); const warmManifest = warmRun.contextSnapshot.paperclipWorkFolders; expect(warmManifest.sandboxKey, "Warm acceptance requires the same physical sandbox").toBe(coldManifest.sandboxKey); await info.attach("cold-and-warm-checkpoints", { contentType: "application/json", body: Buffer.from(JSON.stringify({ cold: cold.saves, warm: warm.saves, coldManifest, warmManifest })) });