From 28a9febe36cb7463a4308f8063ba5f0f159a051b Mon Sep 17 00:00:00 2001 From: Nicky Leach Date: Mon, 26 Oct 2026 10:03:03 -0700 Subject: [PATCH] Cover the agent prerequisite at the test API boundary Update the route fixture for the new plugin declaration lookup and assert that missing agent scope never allocates a lease or falls back to the host. Document preparation and its unchanged actor and task-admission boundaries in the feature and access maps. Verified all 54 environment-test route cases, map structure checks, and token gates. Co-Authored-By: Paperclip --- maps/agent-access-map/agents.md | 10 +++++++++- maps/feature-map/agents.md | 14 ++++++++++++++ .../agent-test-environment-routes.test.ts | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/maps/agent-access-map/agents.md b/maps/agent-access-map/agents.md index 1fa58307434..fc8c7506165 100644 --- a/maps/agent-access-map/agents.md +++ b/maps/agent-access-map/agents.md @@ -50,12 +50,20 @@ permission. Its creator can finish their own inactive setup with that permission changing another person's setup requires configuration permission. Creation retries reuse only records owned by the caller. Returning a completed record also requires the normal agent-read and configuration-read checks. -An unfinished record stays paused with a +An unfinished record keeps compatibility status `paused` with a `setup` hold and cannot receive API keys or authenticate with keys or JWTs. Its standard grants apply at hire. Authorized completion retries recover the same record and approval without changing the submitted configuration. Normal agent-authored hiring keeps its existing approval policy. +An agent-owned plugin may prepare the draft and leave its lifecycle in +`verifying` while that setup hold remains. Only bounded Core-issued setup probes +can run there. The creator's company creation permission also covers configuring +and retrying their draft during preparation; other actors still require the +ordinary configuration permission. This is a board wizard path, not new agent +execution or probe authority. Lifecycle and component regressions cover these +transitions; no permitted-agent or live-provider journey is claimed here. + The creation boundary resolves the request-level runner before adapter validation and credential normalization. Omitted/automatic new Codex selection uses the qualified target's default; explicit Legacy is retained. Disabled adapters and diff --git a/maps/feature-map/agents.md b/maps/feature-map/agents.md index e19eb2205b6..b046bc00856 100644 --- a/maps/feature-map/agents.md +++ b/maps/feature-map/agents.md @@ -37,6 +37,12 @@ Save & exit keeps its configuration; Finish setup on the agent returns to the same record. Final submission requests approval when required, then follows the existing preparation and verification lifecycle. +Agent-owned plugin environments prepare the saved draft in the background when +their driver declares `requiresAgent`. The draft waits in `verifying` with its +setup hold until hiring finishes. Changing environments keeps the selected +account and Configure step, saves the selection, and invalidates its test. +Preparation and failed-save retries remain visible without reconnecting. + ### `agent-configuration` Open `/agents/:agentId` and the configuration/instruction surfaces it exposes. @@ -87,6 +93,14 @@ Preconditions: follow the [baseline](./README.md#before-driving-a-journey). Use Automated: [new agent page](../../ui/src/pages/NewAgent.test.tsx) and [configuration form](../../ui/src/components/AgentConfigForm.render.test.tsx) cover mocked UI behavior. +For an agent-owned plugin environment, verify preparation before connecting, +same-agent probes, account preservation after an environment change, failed +selection-save retry, and switching back while the computer stops. Confirm the +setup hold still blocks ordinary work and API key creation before finishing. +The [lifecycle tests](../../server/src/modules/agent-lifecycle/__tests__/lifecycle.test.ts) +exercise durable transitions with synthetic plugin resources; they do not prove +live VM provisioning. + Manual: Hire an agent with a distinct name and role. Complete any required approval, reload the roster, and open its configuration. Confirm manager, adapter, and model persisted; run a small task to prove execution readiness. For a new Codex hire, choose the Codex harness. Automatic selection uses Paperclip diff --git a/server/src/__tests__/agent-test-environment-routes.test.ts b/server/src/__tests__/agent-test-environment-routes.test.ts index dcc2efacf48..c0006a15919 100644 --- a/server/src/__tests__/agent-test-environment-routes.test.ts +++ b/server/src/__tests__/agent-test-environment-routes.test.ts @@ -46,6 +46,11 @@ const mockEnvironmentRuntime = vi.hoisted(() => ({ })); const mockResolveEnvironmentExecutionTarget = vi.hoisted(() => vi.fn()); +const mockPluginEnvironmentRequiresAgent = vi.hoisted(() => vi.fn(async () => false)); +vi.mock("../services/plugin-environment-driver.js", async importOriginal => ({ + ...(await importOriginal()), + pluginEnvironmentRequiresAgent: mockPluginEnvironmentRequiresAgent, +})); const mockInstanceSettingsService = vi.hoisted(() => ({ get: vi.fn(async () => ({ defaultEnvironmentId: null as string | null })), getGeneral: vi.fn(async () => ({ censorUsernameInLogs: false, executionMode: "local" })), @@ -1053,6 +1058,19 @@ describe("agent test-environment route", () => { mockResolveNewAgentRunnerForCompany.mockClear(); }); + it("reports the missing agent prerequisite without acquiring a lease or probing the host", async () => { + mockPluginEnvironmentRequiresAgent.mockResolvedValueOnce(true); + mockResolveNewAgentRunnerForCompany.mockResolvedValueOnce({ adapterType: "paperclip_runner", adapterConfig: { provider: "codex" } }); + const app = await createApp(); + const res = await request(app) + .post("/api/companies/company-1/adapters/codex_local/test-environment") + .send({ adapterConfig: {}, environmentId: pluginEnvironment.id }); + expect(res.status, JSON.stringify(res.body)).toBe(200); + expect(res.body).toMatchObject({ status: "warn", checks: [{ code: "environment_agent_required", level: "warn" }] }); + expect(mockEnvironmentRuntime.acquireRunLease).not.toHaveBeenCalled(); + expect(testEnvironmentSpy).not.toHaveBeenCalled(); + }); + it("checks the plugin driver against the resolved Runner adapter, not the requested harness", async () => { mockResolveNewAgentRunnerForCompany.mockResolvedValueOnce({ adapterType: "paperclip_runner", adapterConfig: { provider: "acpx", acpxAgent: "claude" } }); const app = await createApp();