From 5a9ce11c49db31440863d633426ebe30bcc850ca Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 12 Oct 2026 17:19:33 -0500 Subject: [PATCH 1/2] fix(release): stage the target contract in assembly fixtures Copy the immutable target JSON into the existing isolated helper fixture without changing negative assertions. Synchronize environment qualification recipes and access limits under the updated map contract. Co-Authored-By: Paperclip --- .../execution-environments.md | 45 +++++++++++++++++++ maps/feature-map/execution-environments.md | 29 ++++++++++++ .../scripts/release-provider-pack.test.mjs | 2 +- 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/maps/agent-access-map/execution-environments.md b/maps/agent-access-map/execution-environments.md index 74860af970f..5c2b2583167 100644 --- a/maps/agent-access-map/execution-environments.md +++ b/maps/agent-access-map/execution-environments.md @@ -10,6 +10,12 @@ Each capability needs operation-level review before its entire scope can be qualified. No live agent execution is claimed. Follow the [map contract](../README.md) for format, permission checks, evidence and maintenance. +Scoped immutable-platform source review: 2026-10-12 at +`d16eccf109b524778ceb896f79ccb3881bc61a9b`, limited to saved sandbox probing, +server-owned qualification metadata and read/invalidation behavior. The original +baseline commit and review depth remain for unrelated operations; no live agent +or provider qualification is claimed by this update. + ## `configuration` Review: `baseline`. @@ -34,6 +40,18 @@ own policy and access review. Gaps: API exists; an agent cannot use instance environment management as an ordinary company actor. +Scoped source review: [environment routes](../../server/src/routes/environments.ts) +require board organization access for reads and local-implicit board or instance +administrator for management. Create/PATCH reject client-supplied platform-proof +metadata; [service updates](../../server/src/services/environments.ts) discard +supplied proof, preserve current server proof atomically for metadata-only edits, +and invalidate it when the target configuration or driver changes. Public row +responses hide the private record. This is source-only at the scoped commit. +Verify forged metadata rejection, concurrent probe/update preservation, target +invalidation and restricted-reader redaction; actual agent-denial proof remains +not run. Company/team packages do not import or export environment rows, so those +packages do not transfer platform qualification. + ## `capabilities` Review: `baseline`. @@ -73,6 +91,15 @@ Agent access: `restricted` — Board/admin probes / CLI environment. Policy: Environment management is instance administrator/board; permitted assignment uses agent/project policy. +Both probe endpoints require a board actor that is local-implicit or an instance +administrator; ordinary agent identity and company membership alone are denied. +The saved sandbox probe also resolves company/environment and secret access +before allocating a fresh lease. Its measurement is company-bound to an immutable +image digest, effective provider configuration and active custom-image identity; +only confirmed cleanup permits recording it. A draft probe does not record saved +qualification. Reading stored qualification does not lease/provision a target and +does not grant an agent permission to probe or manage that target. + Evidence: `source-only` — [implementation](../../server/src/routes/environments.ts#L353) at the reviewed commit above. @@ -82,6 +109,24 @@ own policy and access review. Gaps: Reachability probes are not granted by an agent's right to execute assigned work. +Scoped Evidence: `source-only` — [probe authorization](../../server/src/routes/environments.ts), +[immutable binding, measurement and read](../../server/src/services/environment-probe.ts), +[private metadata writes](../../server/src/services/environments.ts) and +[custom-image binding](../../server/src/services/environment-custom-image-runtime.ts) +at the scoped commit. A connection-only success or a fixture cannot certify +native compatibility, account authentication or agent access. + +Scoped Verification: `not-run` — on one authorized owned target, compare pinned +image proof with mutable/snapshot and changed-config controls; require independent +cleanup evidence before recording proof. Assert agent/non-admin board denial, +secret-context denial where the actor lacks access and forged-proof rejection. +Verify API reads hide the private record and metadata-only updates cannot overwrite it. Then use +[runtime validation](./runs-adapters.md#validation) for account/model/native startup. + +There is no new delegated agent probe binding or client proof-write API. Any +human-action request/result handoff requires its own admitted path and verification; +an accepted interaction does not turn the caller into a board administrator. + ## `assignment` Review: `baseline`. diff --git a/maps/feature-map/execution-environments.md b/maps/feature-map/execution-environments.md index bb1fa10d78f..dda710a1d89 100644 --- a/maps/feature-map/execution-environments.md +++ b/maps/feature-map/execution-environments.md @@ -38,6 +38,33 @@ Automated: [environment UI](../../ui/src/pages/CompanyEnvironments.test.tsx), [d Manual: Save a disposable target, reload it, and run its available probe. Verify both success and missing-credential/reachability errors identify the target. Confirm unavailable providers are not offered as working run targets. +For an immutable sandbox image, pin the OCI image with `@sha256` and explicitly +probe the saved environment in the intended company. The existing connection +probe allocates a fresh lease and can measure its OS, architecture and libc. +Qualification is recorded only when the selected image/configuration matches +the live lease and cleanup is confirmed. Inspect `platformQualificationRecorded` +and its diagnostic in the probe result: connection success alone does not mean +the image has native runtime qualification. + +The measured record is server-owned, company-bound and tied to the effective +configuration and active custom-image identity. Mutable tags, provider defaults +and name-only snapshots do not qualify. Changing the target or image invalidates +the record; normal metadata edits cannot forge or overwrite it, and API reads +omit the private metadata. Reading this evidence for discovery or creation does +not allocate a sandbox. The draft `probe-config` checks are not a saved-image +qualification operation. See the [runtime contract](../../packages/paperclip-runner/README.md) +and [adapter validation recipe](./runs-adapters.md#adapter-setup) for native +binary/provider readiness after platform qualification. + +Verification recipe: probe a pinned owned image, record its company/config/image +identity and cleanup result, then change the image or target and verify its old +qualification is not reused. A mutable-image connection can succeed while +qualification remains absent. Assert ordinary agent and non-admin board probe +requests are denied, and environment create/PATCH payloads cannot supply +qualification metadata. +This recipe and its [probe controls](../../server/src/__tests__/environment-probe.test.ts) +are not claims that a live provider or agent-access journey ran for this map update. + The local row has no UI Test button. Its API/CLI probe reports host/cwd availability only; it does not validate an adapter binary, workspace, or task execution. Remote-driver probes and a real bounded run need their own evidence. @@ -51,5 +78,7 @@ Manual: Run a bounded command in the intended target, inspect its output and wor ## Gotchas - A probe is narrower evidence than a complete task with tools, files, and resumption. +- Platform qualification does not authenticate a model account or qualify every + harness; the selected native runtime still needs its own setup and useful-task proof. - The local option may appear only when editing an existing local environment; availability follows current UI/provider capabilities. - Workspace service reachability and environment reachability are separate checks. diff --git a/packages/paperclip-runner/scripts/release-provider-pack.test.mjs b/packages/paperclip-runner/scripts/release-provider-pack.test.mjs index 5dded2a32fd..171edd46f06 100644 --- a/packages/paperclip-runner/scripts/release-provider-pack.test.mjs +++ b/packages/paperclip-runner/scripts/release-provider-pack.test.mjs @@ -52,7 +52,7 @@ test("release assembly rejects rehashed stale or missing packs before staging an const source = dirname(dirname(fileURLToPath(import.meta.url))); try { // Run the actual assembler in an isolated layout so it cannot overwrite built artifacts. - for (const file of ["scripts/stage-release-runner-binaries.mjs", "scripts/release-provider-pack.mjs", "src/live/runner-binary.ts", "acpx-profiles.json", "cursor-distributions.json"]) { + for (const file of ["scripts/stage-release-runner-binaries.mjs", "scripts/release-provider-pack.mjs", "src/live/runner-binary.ts", "acpx-profiles.json", "cursor-distributions.json", "release-runner-targets.json"]) { mkdirSync(dirname(join(root, file)), { recursive: true }); cpSync(join(source, file), join(root, file)); } const platforms = {}; From fe1ecf1c3449c21d6e6f0c9d9772d1949b80ea82 Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 12 Oct 2026 17:21:16 -0500 Subject: [PATCH 2/2] fix(agents): retain Legacy ACP network restrictions during selection Reject restrictive or unresolved ACP network settings with field-specific Legacy guidance instead of silently discarding them. Preserve unused CLI flags, recorded native profiles and trusted server policy. Synchronize the required feature and access maps without claiming live qualification. Co-Authored-By: Paperclip --- maps/agent-access-map/agents.md | 41 +++++++++++ maps/agent-access-map/onboarding.md | 30 ++++++++ maps/agent-access-map/runs-adapters.md | 37 ++++++++++ maps/feature-map/agents.md | 27 +++++++- maps/feature-map/onboarding.md | 16 +++++ maps/feature-map/runs-adapters.md | 32 +++++++++ .../services/agent-runner-selection.test.ts | 68 ++++++++++++++++++- server/src/services/agent-runner-selection.ts | 12 ++++ 8 files changed, 261 insertions(+), 2 deletions(-) diff --git a/maps/agent-access-map/agents.md b/maps/agent-access-map/agents.md index 46c3ee462a2..0d44f87b62a 100644 --- a/maps/agent-access-map/agents.md +++ b/maps/agent-access-map/agents.md @@ -9,6 +9,12 @@ Each capability needs operation-level review before its entire scope can be qualified. No live agent execution is claimed. Follow the [map contract](../README.md) for format, permission checks, evidence and maintenance. +Scoped runner-default source review: 2026-10-12 at +`eee0534154eaf5d0d3f2bbc97c1e24d86f34a5a5`, limited to creation/configuration, +approval activation, built-in provisioning and import preservation described +below. The original baseline commit above still governs unrelated entries; this +update does not claim operation-complete review or live agent verification. + ## `hire` Review: `baseline`. @@ -24,17 +30,45 @@ native hires. Policy: Agent creation/configuration uses action grants, resource policies and change consent; some controls require a board actor. +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 +external overrides affect new selection, while missing credentials/runtime or +incompatible settings remain errors. Existing agents and reviewed pending hires +retain their recorded execution. Native `hire_agent` explicitly inherits its +caller's runtime and remains subject to permission and credential restrictions; +it is not an arbitrary host-configuration or login tool. REST/CLI creation and +hire runner options use the [shared request contract](../../packages/shared/src/validators/agent.ts). + Evidence: `source-only` — [implementation](../../server/src/routes/agents.ts#L1395) at the reviewed commit above. +Scoped Evidence: `source-only` — [creation policy](../../server/src/services/agent-hiring.ts), +[runner resolution](../../server/src/services/agent-runner-selection.ts), +[native hire binding](../../server/src/services/native-runtime/paperclip-runner-tool-authority.ts), +[frozen approval activation](../../server/src/modules/agent-lifecycle/adapters/approvals.ts) +and [same-harness import preservation](../../server/src/services/company-portability.ts) +at the scoped commit. No permitted-agent journey was executed for this map update. + Verification: `not-run` — exercise a listed permitted operation with actual agent credentials/run context; inspect its result and applicable persistence/attribution. Assert the relevant denied actor, owner, company or mode from Policy. Review the remaining operations before qualifying the whole capability. +For this scope, compare automatic and explicit Legacy creation, assert foreign +company/environment and missing-grant denials, then compare pending approval +configuration across restart and activation. An unrelated edit/import update must +not change existing execution; a new package without a runner choice receives +creation defaults. Do not count a board call or component test as agent evidence. + Gaps: Identity/persona hiring works. Operator account login and sensitive adapter/environment configuration are separate guarded actions. +The runner request does not grant new account, environment-probe or native API +fallback authority; see [validation access](./runs-adapters.md#validation) and +[environment administration](./execution-environments.md#probe). Full access +qualification remains not run. + ## `instructions` Review: `baseline`. @@ -138,3 +172,10 @@ remaining operations before qualifying the whole capability. Gaps: Provisioning checks agents:create. Enabling/running its built-in routine is not the same permission path. + +Scoped source review: [built-in provisioning](../../server/src/services/built-in-agents.ts) +recognizes the underlying Codex harness of a native company agent. New configured +provisioning resolves the runner, while unrelated reconciliation and same-harness +edits retain existing execution. Verify explicit runner changes preserve compatible +saved settings; the general creation/access policy above still applies. This is +source-only evidence at the scoped commit, not a live provisioning result. diff --git a/maps/agent-access-map/onboarding.md b/maps/agent-access-map/onboarding.md index f4eb025f50c..c3c4f3429df 100644 --- a/maps/agent-access-map/onboarding.md +++ b/maps/agent-access-map/onboarding.md @@ -9,6 +9,11 @@ Each capability needs operation-level review before its entire scope can be qualified. No live agent execution is claimed. Follow the [map contract](../README.md) for format, permission checks, evidence and maintenance. +Scoped runner-default source review: 2026-10-12 at +`eee0534154eaf5d0d3f2bbc97c1e24d86f34a5a5`, limited to first-agent selection, +selected-runtime validation and draft restoration. Unrelated baseline entries +retain the original source commit; no live agent verification is added. + ## `instance` Review: `baseline`. @@ -74,10 +79,25 @@ provider-account/environment setup. Policy: Instance setup is operator work; company creation is board; agent creation/task creation retain their individual permissions. +The wizard selects the Codex harness and lets the server resolve an automatic +new-agent runner; explicit Legacy remains available. Supported-platform and +immutable sandbox evidence follow [environment probe policy](./execution-environments.md#probe). +Creation/discovery do not provision a target to determine this default. Adapter +setup validation requires `agents:create`, plus saved-agent update access when +testing an existing agent; interactive account login additionally requires a +board user and owner-bound session. An agent's right to hire is not login or +instance-environment administration authority. + Evidence: `source-only` — [implementation](../../ui/src/components/OnboardingWizard.tsx#L279) at the reviewed commit above. +Scoped Evidence: `source-only` — [wizard/default selection](../../ui/src/components/OnboardingWizard.tsx), +[creation resolver](../../server/src/services/agent-runner-selection.ts) and +[setup/login authorization](../../server/src/routes/agents.ts) at the scoped +commit. Existing agent/approval execution preservation follows +[hiring access](./agents.md#hire); no live onboarding proof is claimed here. + Verification: `not-run` — exercise a listed permitted operation with actual agent credentials/run context; inspect its result and applicable persistence/attribution. Assert the relevant denied actor, owner, company or mode from Policy. Review the @@ -86,6 +106,11 @@ remaining operations before qualifying the whole capability. Gaps: Account authorization, environment administration and model selection need separate checks. +Verify automatic onboarding without a post-hire runner change, explicit Legacy, +saved configuration after reload and the actual selected runtime's useful task +plus follow-up. Assert denied agent login/probe and foreign-company account or +environment access separately. These agent-context checks remain `not-run`. + ## `first-task` Review: `baseline`. @@ -134,3 +159,8 @@ evidence. Gaps: No server onboarding-draft API for this browser-local state; classify explicitly or intentionally make it a cross-client product feature. + +Scoped source review of [draft restoration](../../ui/src/components/OnboardingWizard.tsx) +preserves harness/runner intent and invalidates obsolete setup-test results after +a selection change. Browser draft continuity is not API or live-agent evidence, +and opening a saved Legacy agent does not apply new-agent defaults. diff --git a/maps/agent-access-map/runs-adapters.md b/maps/agent-access-map/runs-adapters.md index bd2f509c545..62a99f42b3a 100644 --- a/maps/agent-access-map/runs-adapters.md +++ b/maps/agent-access-map/runs-adapters.md @@ -9,6 +9,11 @@ Each capability needs operation-level review before its entire scope can be qualified. No live agent execution is claimed. Follow the [map contract](../README.md) for format, permission checks, evidence and maintenance. +Scoped runner-default source review: 2026-10-12 at +`eee0534154eaf5d0d3f2bbc97c1e24d86f34a5a5`, limited to runner discovery, +selected-runtime testing and saved execution/session preservation. The original +baseline source and review depth remain unchanged for unrelated operations. + ## `availability` Review: `baseline`. @@ -35,6 +40,15 @@ remaining operations before qualifying the whole capability. Gaps: Raw instance adapter listing requires board, while company model discovery exists for eligible agents. +Scoped source-only evidence: [adapter discovery](../../server/src/routes/adapters.ts) +reports supported runners/effective default for the selected company/environment; +[selection policy](../../server/src/services/agent-runner-selection.ts) reads +server-owned immutable sandbox facts without provisioning. New Codex defaults +and explicit overrides follow the [shared runner contract](../../packages/shared/src/agent-runner.ts); +other harness defaults remain unchanged. The board inventory does not grant a +native agent an equivalent discovery binding. See +[probe restrictions](./execution-environments.md#probe) for acquiring qualification. + ## `validation` Review: `baseline`. @@ -60,6 +74,22 @@ remaining operations before qualifying the whole capability. Gaps: Environment test path checks creation/configuration policy; missing provider auth must use its owner setup flow. +Scoped source review: [test authorization and resolution](../../server/src/routes/agents.ts) +requires same-company `agents:create`, saved-agent update access when supplied, +allowed environment selection, and normal secret/responsible-user checks. An +unchanged saved runner avoids new-creation policy but still receives selected +runtime validation. [Native readiness](../../server/src/services/native-runtime/setup-readiness.ts) +tests native Codex startup/model/account and bounded teardown; an authentication +or Legacy CLI check cannot replace that runtime evidence. Missing dependencies, +credentials, invalid models and incompatible settings fail without silently +changing runners. Account login remains board/owner-bound. + +Verification for this scope remains `not-run`: use an admitted agent REST/CLI +validation path, assert missing grants and cross-company targets/accounts are +denied, then prove the useful task/follow-up separately. `hire_agent` inherits its +caller's runtime; do not assume its binding accepts arbitrary runner or account +settings. See [creation access](./agents.md#hire). + ## `runs` Review: `baseline`. @@ -109,3 +139,10 @@ own policy and access review. Gaps: Execution context reset is not a permitted generic native API mutation or an ordinary agent management path. + +Scoped source-only evidence: [configuration changes](../../server/src/services/agent-configuration.ts) +and [native execution/recovery](../../server/src/services/native-runtime/native-session-executor.ts) +retain existing execution on unrelated edits and use recorded run configuration +for active/recovered runs. Explicit runner changes use revisions and session +invalidation. Verify saved configuration, current versus historical run identity, +restart and follow-up without granting session-reset authority to an agent. diff --git a/maps/feature-map/agents.md b/maps/feature-map/agents.md index 673d7c45982..500a88e49db 100644 --- a/maps/feature-map/agents.md +++ b/maps/feature-map/agents.md @@ -8,7 +8,7 @@ Agent access: [companion](../agent-access-map/agents.md). ## Sub-features -- `hire`: choose identity, adapter, model, and required account settings; edit +- `hire`: choose identity, harness, model, and required account settings; edit reporting relationships through the agent configuration afterward. - `instructions`: edit agent configuration and instruction files with durable revisions. - `organization`: inspect and change reporting relationships through supported controls. @@ -39,6 +39,25 @@ Automated: [new agent page](../../ui/src/pages/NewAgent.test.tsx) and [configura 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 +Runner on a qualified selected target; Advanced offers an explicit Legacy runner +override. Other harness defaults are unchanged. Record the persisted runner and +underlying harness, rather than treating the displayed Codex name as execution +proof. See [runtime selection and validation](./runs-adapters.md#adapter-setup) +and [target qualification](./execution-environments.md#environment-settings). + +Repeat with an explicit Legacy Codex hire. If approval is required, compare its +reviewed execution configuration before and after restart and activation; approval +must retain the reviewed runner. Test an existing Legacy and native agent through +an unrelated edit, reload and restart: neither is automatically upgraded. + +Creation defaults also apply to API/CLI hires, built-ins, plugins and new team +imports. Exports preserve resolved execution choices; a package without a runner +choice uses the new-agent default, while updating an existing same-harness agent +preserves its execution. For a native Codex import, edit a model or account binding +and verify unchanged runtime settings survive; an explicit clear must stay cleared. +Use the [portability recipe](./companies.md) for package preparation and boundaries. + The current hiring wizard assigns the first hire CEO and subsequent hires General reporting to the CEO. Change manager/role afterward through supported configuration. Process/HTTP agents can be created through API/CLI but are excluded @@ -50,6 +69,12 @@ Automated: [instruction service](../../server/src/__tests__/agent-instructions-s Manual: Save a harmless instruction edit, reload, and verify its revision and effect in the next eligible run. Pause and resume the disposable agent; confirm pause prevents new work and does not erase previous runs. +An explicit runner change is a configuration change, with normal revisions and +session invalidation. Incompatible settings must produce a field-specific error; +selecting another runner must not silently discard them or change an active run's +recorded execution. Keep runner choice in Advanced and verify the next eligible +run separately from historical transcripts. + ### `organization` Automated: [org chart](../../ui/src/pages/OrgChart.test.tsx) is component evidence. Built-in setup also has [configuration modal tests](../../ui/src/components/ConfigureBuiltInAgentModal.test.tsx). diff --git a/maps/feature-map/onboarding.md b/maps/feature-map/onboarding.md index ecfaeefaaef..5a411e3ebfa 100644 --- a/maps/feature-map/onboarding.md +++ b/maps/feature-map/onboarding.md @@ -50,6 +50,22 @@ company's onboarding route, and finish; drafts save automatically. Follow the created first task, verify the selected agent and company, and inspect its output or explicit authentication gate. Reload both company and task. +For Codex, choose the harness and leave runner selection automatic to prove the +first-agent default. On a qualified target it saves Paperclip Runner; Advanced +also permits Legacy. Do not switch runners after onboarding when claiming proof +of that default. Test the selected runtime and account, then complete a bounded +useful task and follow-up with an independently checked file artifact. Saved +execution configuration and recorded run identity must match after reload. +Use [runner validation](./runs-adapters.md#adapter-setup) and +[environment qualification](./execution-environments.md#environment-settings) +for the prerequisites; a successful login alone is insufficient. + +Leave and resume during harness/runner setup. Confirm the draft retains the +explicit choice and compatible account bindings, old native Codex setup links +recover the harness and runner, and a changed harness or runner invalidates stale +test results. Existing Legacy agents remain Legacy when their onboarding/setup +configuration is reopened; this journey does not migrate them. + ### `cli-setup` Automated: [CLI onboarding](../../cli/src/__tests__/onboard.test.ts) and [isolated test drive](../../cli/src/__tests__/test-drive.test.ts) cover their setup contracts; they do not establish that every model account is usable. diff --git a/maps/feature-map/runs-adapters.md b/maps/feature-map/runs-adapters.md index bec86d34f2c..453e2f81c63 100644 --- a/maps/feature-map/runs-adapters.md +++ b/maps/feature-map/runs-adapters.md @@ -33,12 +33,44 @@ Automated: [adapter validation routes](../../server/src/__tests__/agent-adapter- Manual: Select the intended adapter and account, refresh model choices, and run its environment test. Save, reload, then execute a small task. Repeat with a deliberately unavailable account and verify the failure remains visible. +Choose Codex as the harness; the UI displays its name/icon for both runners. +Advanced identifies the automatic default and offers Paperclip Runner or Legacy. +Only new Codex agents default to the native runner on supported macOS or GNU/Linux +targets; the [shared selection contract](../../packages/shared/src/agent-runner.ts) +and [runtime prerequisites](../../packages/paperclip-runner/README.md) define the +platform/libc boundary. Windows and Alpine/musl remain Legacy; other harness +defaults are unchanged. Disabled adapters, external overrides, and selected-target +qualification also affect availability. + +For sandbox defaults, follow the [environment probe](./execution-environments.md#environment-settings) +recipe first. Discovery/creation read company-bound immutable-image evidence +without allocating a target. A mutable image or snapshot name does not establish +native compatibility. Then test the actual selected runtime with its selected +account/model: a legacy CLI or authentication probe alone cannot establish native +readiness. Missing dependencies, failed authentication, invalid models and +incompatible custom settings must remain actionable errors without silent runner +switching. Saved unchanged execution can still be tested without applying new +selection policy, but runtime/account validation must still run. + +Verify an automatic Codex hire and an explicit Legacy control through a useful +file artifact, follow-up and reload. Preserve Codex reasoning effort and account +isolation; retain explicit Legacy permission choices, including false. Reopen an +existing agent, make an unrelated edit, and confirm both its persisted runner and +next run stay unchanged. Use [hiring/import preservation](./agents.md#hire-agent) +for approval and portability controls. + ### `run-history` Automated: [live run routes](../../server/src/__tests__/agent-live-run-routes.test.ts) and [run event sequencing](../../server/src/__tests__/heartbeat-run-event-sequencing.test.ts) cover run data contracts. Manual: Start a disposable task, open its running transcript, and follow it to a terminal outcome. Reload and compare task/run IDs, status, output, and cost. Verify a failed run remains distinguishable from a completed run with useful output. +An explicit runner change starts a configuration revision and invalidates the +eligible session. Active runs, restart recovery and historical transcript parsing +retain their recorded execution configuration; do not infer their runner from a +later edit to the agent. Account/harness branding and execution identity are +different observations, both required in the evidence. + ## Gotchas - Model accounts are managed in Apps and inline agent account controls. diff --git a/server/src/services/agent-runner-selection.test.ts b/server/src/services/agent-runner-selection.test.ts index b366bcd57b5..28b2a369608 100644 --- a/server/src/services/agent-runner-selection.test.ts +++ b/server/src/services/agent-runner-selection.test.ts @@ -9,7 +9,8 @@ vi.mock("./environment-probe.js", () => ({ readSandboxPlatformQualification: sta vi.mock("./environment-config.js", () => ({ resolveEnvironmentDriverConfigForRuntime: state.resolveEnvironment })); vi.mock("@paperclipai/adapter-utils/ssh", () => ({ runSshCommand: state.ssh })); import { AGENT_RUNNER_SSH_PLATFORM_COMMAND, agentRunnerAvailability, resolveAgentRunnerTargetForCompany, resolveNewAgentRunner, resolveNewAgentRunnerForCompany } from "./agent-runner-selection.js"; -import { buildCodexAcpConfig } from "@paperclipai/adapter-codex-local/server"; +import { buildCodexAcpConfig, resolveCodexExecutionEngine } from "@paperclipai/adapter-codex-local/server"; +import { secretService } from "./secrets.js"; import { localAgentRunnerTarget } from "../lib/agent-runner-platform.js"; const target = { driver: "local", platform: "linux", architecture: "x64" }; const qualifiedTargets = [target, { driver: "local", platform: "linux", architecture: "arm64" }, { driver: "local", platform: "darwin", architecture: "arm64" }, { driver: "local", platform: "darwin", architecture: "x64" }]; @@ -183,6 +184,71 @@ describe("server-owned Codex runner selection", () => { .toThrow(expect.objectContaining({ status: 422, details: { code: "agent_runner_config_incompatible", fields: ["acpPermissionMode"] } })); }); + it.each([undefined, "acp"])("rejects effective Legacy ACP network denial for engine=%s before native selection", async engine => { + const adapterConfig = { ...(engine ? { engine } : {}), env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" } }; + expect(resolveCodexExecutionEngine(adapterConfig).engine).toBe("acp"); + expect(buildCodexAcpConfig(adapterConfig)).toMatchObject({ env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" } }); + // Native permissions use a separately trusted server variable. This would + // enable networking despite the saved Legacy flag without admission rejection. + const { codexNetworkAccess } = await import(new URL( + "../../../packages/paperclip-runner/src/drivers/codex/codex-security-config.ts", import.meta.url, + ).href); + expect(codexNetworkAccess({ PAPERCLIP_RUNNER_NETWORK_ACCESS: "enabled", PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" })).toBe(true); + for (const runner of [undefined, "auto", "paperclip"] as const) { + expect(() => resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target, runner })) + .toThrow(expect.objectContaining({ status: 422, details: { code: "agent_runner_config_incompatible", fields: ["env.PAPERCLIP_CODEX_ACP_NETWORK_ACCESS"] } })); + } + expect(resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target, runner: "legacy" })) + .toEqual({ adapterType: "codex_local", adapterConfig }); + expect(adapterConfig.env).toEqual({ PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" }); + }); + + it.each([ + { type: "plain", value: "false" }, + { type: "secret_ref", secretId: "fixture-policy", version: "latest" }, + { type: "user_secret_ref", key: "fixture-policy", version: "latest" }, + ])("does not discard a bound ACP network restriction or guess its value (%j)", async binding => { + const adapterConfig = { engine: "acp", env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: binding } }; + if (binding.type === "plain") { + const resolved = await secretService({} as never).resolveEnvBindings("fixture-company", adapterConfig.env); + expect(buildCodexAcpConfig({ ...adapterConfig, env: resolved.env })) + .toMatchObject({ env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" } }); + } + expect(() => resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target })) + .toThrow(expect.objectContaining({ status: 422, details: { code: "agent_runner_config_incompatible", fields: ["env.PAPERCLIP_CODEX_ACP_NETWORK_ACCESS"] } })); + expect(resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target, runner: "legacy" })) + .toEqual({ adapterType: "codex_local", adapterConfig }); + }); + + it.each([ + { extraArgs: ["-c", "sandbox_workspace_write.network_access=true"] }, + { extraArgs: [], args: ["-c", "sandbox_workspace_write.network_access=true"] }, + ])("retains actionable argument errors when Legacy arguments override a network flag (%j)", args => { + const adapterConfig = { engine: "acp", ...args, env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" } }; + expect(buildCodexAcpConfig(adapterConfig)).toMatchObject({ env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "true" } }); + expect(() => resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target })) + .toThrow(expect.objectContaining({ status: 422, details: { code: "agent_runner_config_incompatible", fields: [args.extraArgs.length ? "extraArgs" : "args"] } })); + }); + + it.each(["true", "False", " false ", { type: "plain", value: "true" }])("keeps compatible ACP environment settings (%j) and native idempotence", value => { + const adapterConfig = { engine: "acp", env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: value, OPENAI_API_KEY: { type: "secret_ref", secretId: "fixture-api-key" } } }; + const literal = typeof value === "string" ? value : value.value; + expect(buildCodexAcpConfig({ ...adapterConfig, env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: literal } })) + .toMatchObject({ env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "true" } }); + const resolved = resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target }); + expect(resolved.adapterType).toBe("paperclip_runner"); + expect(resolved.adapterConfig.env).toEqual(adapterConfig.env); + expect(resolveNewAgentRunner(resolved)).toEqual(resolved); + }); + + it("keeps a saved native profile and an explicitly selected Legacy CLI's unused ACP flag unchanged", () => { + const adapterConfig = { engine: "cli", env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" } }; + expect(resolveCodexExecutionEngine(adapterConfig).engine).toBe("cli"); + expect(resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target }).adapterType).toBe("paperclip_runner"); + const saved = { adapterType: "paperclip_runner", adapterConfig: { provider: "codex", env: adapterConfig.env } }; + expect(resolveNewAgentRunner(saved)).toEqual(saved); + }); + it("preserves explicit historical native profiles, including managed configurations", () => { for (const config of [{ provider: "acpx", acpxAgent: "claude", model: "claude-sonnet-5" }, { provider: "claude_managed", managedProfileId: "profile", managedAgentsRetentionAcknowledged: true }, { provider: "aws_agentcore", agentCoreProfileId: "profile" }]) expect(resolveNewAgentRunner({ adapterType: "paperclip_runner", adapterConfig: config })).toEqual({ adapterType: "paperclip_runner", adapterConfig: config }); }); diff --git a/server/src/services/agent-runner-selection.ts b/server/src/services/agent-runner-selection.ts index d2e7c8e61cd..1c804c53f59 100644 --- a/server/src/services/agent-runner-selection.ts +++ b/server/src/services/agent-runner-selection.ts @@ -96,6 +96,18 @@ export function resolveNewAgentRunner(input: AgentRunnerSelection): { adapterTyp if (config.dangerouslyBypassApprovalsAndSandbox === false) unsupported.push("dangerouslyBypassApprovalsAndSandbox"); if (config.dangerouslyBypassSandbox === false) unsupported.push("dangerouslyBypassSandbox"); if (unsupported.length) incompatible(unsupported.map(field => aliasSources.get(field) ?? field)); + const env = config.env && typeof config.env === "object" && !Array.isArray(config.env) + ? config.env as Record : {}; + const network = env.PAPERCLIP_CODEX_ACP_NETWORK_ACCESS; + const binding = network && typeof network === "object" && !Array.isArray(network) + ? network as Record : null; + // Native networking is server-owned. Never translate a Legacy restriction + // into that trusted variable, or guess the value of a deferred policy binding. + if (config.engine !== "cli" && (network === "false" + || (binding?.type === "plain" && binding.value === "false") + || binding?.type === "secret_ref" || binding?.type === "user_secret_ref")) { + incompatible(["env.PAPERCLIP_CODEX_ACP_NETWORK_ACCESS"]); + } const effort = config.modelReasoningEffort ?? config.reasoningEffort ?? config.effort; for (const key of ["engine", "command", "extraArgs", "args", "agentCommand", "mode", "nonInteractivePermissions", "stateDir", "warmHandleIdleMs", "dangerouslyBypassApprovalsAndSandbox", "dangerouslyBypassSandbox", "search", "fastMode", "reasoningEffort", "effort"]) delete config[key]; if (config.model !== undefined && typeof config.model !== "string") incompatible(["model"]);