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 <noreply@paperclip.ing>
This commit is contained in:
Nicky LeachandPaperclip committed 2026-10-26 14:30:41 -07:00
1 parent 73185c63fe
commit 28a9febe36
3 files changed
+41 -1

No files matched your search

+9 -1
View File
@@ -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
+14
View File
@@ -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
@@ -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<typeof import("../services/plugin-environment-driver.js")>()),
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();