merge: preserve shared network admission and map contracts

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-12 17:25:10 -05:00
commit d8253b2a2c
11 files changed
+336 -3

No files matched your search

+41
View File
@@ -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`.
@@ -30,17 +36,45 @@ bypass hiring authorization. Source: [runner resolver](../../server/src/services
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`.
@@ -144,3 +178,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.
@@ -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`.
+30
View File
@@ -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.
+37
View File
@@ -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`.
@@ -40,6 +45,15 @@ Source: [adapter discovery](../../server/src/adapters/registry.ts).
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`.
@@ -65,6 +79,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`.
@@ -114,3 +144,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.
+26 -1
View File
@@ -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. 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.
For Claude Code on Apple Silicon, submit without a runner override and inspect
saved configuration: `paperclip_runner`, `provider: acpx`, `acpxAgent: claude`,
and ordinary model `claude-opus-5-5`. The Advanced runner selector can explicitly
@@ -58,6 +77,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).
@@ -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.
+16
View File
@@ -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.
+32
View File
@@ -37,12 +37,44 @@ models/authentication and unsupported Legacy-only options must fail visibly.
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.
@@ -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 = {};
@@ -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 });
});
@@ -155,6 +155,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<string, unknown> : {};
const network = env.PAPERCLIP_CODEX_ACP_NETWORK_ACCESS;
const binding = network && typeof network === "object" && !Array.isArray(network)
? network as Record<string, unknown> : 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"]);