Files
PaperClipAI/server/src/__tests__/general-capability-classifier.test.ts
T
Nicky LeachandPaperclip a14e51d592 refactor(environment): classify environment capabilities from static driver definitions (#12045)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Environment runtime drivers provide workspace, lease, and custom
image behavior
> - Runtime code used driver identity checks and several
capability-specific members
> - These checks spread capability rules across the runtime and made new
drivers harder to verify
> - This pull request adds one general capability classifier and one
static driver support table
> - The benefit is one fail-closed capability model that keeps current
behavior and supports future drivers

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Environment runtime capability checks for workspace realization, custom
images, lease capabilities, and duplex authorization.

**Subsystem affected**

Cross-cutting (multiple of the above)

**Current behavior**

The runtime selects several capability paths from driver identity and
separate capability members. Custom image gates also trust provider
declarations without checking every matching live worker method.

**Proposed behavior**

The runtime uses one general capability classifier and one static
support table. Custom image gates require both the provider declaration
and every matching live worker method. The public capability names
remain unchanged.

**Reason and benefit**

The change keeps capability rules in one place. It removes identity
conditions from runtime consumers and makes unsupported drivers fail
closed.

**Breaking changes**

None. The public names sandboxCapabilities, sandboxProviders, and
EffectiveSandboxCapabilities remain available.

## What Changed

- Add classifyEnvironmentCapabilities and static support definitions for
all four driver families.
- Add resolveCapabilities to every environment runtime driver.
- Move driver traits into environment-driver-traits.ts and migrate
runtime consumers.
- Require provider declarations and matching live worker methods for all
custom image gates.
- Migrate duplex authorization to the general resolver and remove the
dead sandbox-only member.
- Delete the unused resolveEffectiveSandboxCapabilities wrapper and
update its test.

## Verification

- pnpm --filter @paperclipai/server typecheck
- pnpm exec vitest run
server/src/__tests__/environment-capability-contract.test.ts
server/src/__tests__/environment-runtime.test.ts — 92 tests pass
- pnpm exec vitest run
server/src/__tests__/environment-driver-traits.test.ts
server/src/__tests__/general-capability-classifier.test.ts — 12 tests
pass
- pnpm exec vitest run
server/src/__tests__/environment-custom-images-service.test.ts
server/src/__tests__/environment-execution-target-capabilities.test.ts
server/src/__tests__/environment-execution-target-duplex.test.ts
server/src/__tests__/environment-execution-target-duplex-kill-switch.test.ts
— 66 tests pass

## Risks

The main risk is a capability gate that denies a valid driver or permits
an invalid driver. The static support matrix, live worker method checks,
and regression tests reduce this risk. No database, public API, or
published type name changes.

## Model Used

OpenAI Codex, GPT-5, with tool use and code execution. The deployment
does not provide a separate context-window value.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with Fixes: # / Closes #
/ Refs # OR (b) described the issue in-PR following the relevant issue
template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub #NNN / github.com/paperclipai/paperclip URLs)
- [x] My branch name describes the change (for example, docs/... or
fix/...) and contains no internal Paperclip ticket id or
instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-23 21:24:59 -07:00

127 lines
5.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT,
SANDBOX_CAPABILITY_KEYS,
classifyEnvironmentCapabilities,
} from "../services/environment-runtime.js";
// The worker verbs a fully-capable provider advertises. A built-in driver maps
// its own methods onto these verb names, so the general classifier reads one
// verb vocabulary for every driver.
const ALL_PROVIDER_METHODS = [
"environmentResumeLease",
"environmentReleaseLease",
"environmentDestroyLease",
"environmentExecute",
"environmentSyncIn",
"environmentSyncOut",
"duplexChannelOpen",
];
describe("general capability classifier", () => {
it("returns the eight Boolean fields for a full sandbox declaration input", () => {
// A sandbox provider that verifies every prerequisite verb and declares every
// capability resolves the whole eight-field set to true. The classifier reads
// the sandbox driver's static support definition, so it names no driver.
const effective = classifyEnvironmentCapabilities({
verifiedMethods: ALL_PROVIDER_METHODS,
declared: {
reusableLeases: true,
nativeSyncIn: true,
nativeSyncOut: true,
persistentProcessSessions: true,
independentControlCommands: true,
incrementalSessionOutput: true,
concurrentSyncOperations: true,
duplexCommandStream: true,
},
supportedCapabilities: ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.sandbox.supportedCapabilities,
});
// The result carries exactly the eight capability fields, each a Boolean.
expect(Object.keys(effective).sort()).toEqual([...SANDBOX_CAPABILITY_KEYS].sort());
for (const key of SANDBOX_CAPABILITY_KEYS) {
expect(typeof effective[key]).toBe("boolean");
expect(effective[key]).toBe(true);
}
});
it("default rule one: an absent declaration defers to verification for a worker-property field", () => {
// A worker-property capability has no opt-in declaration. An absent
// declaration defers to the verified baseline, so a verified verb grants the
// capability and an unverified verb denies it.
const effective = classifyEnvironmentCapabilities({
verifiedMethods: ["environmentSyncIn", "environmentSyncOut"],
declared: null,
supportedCapabilities: ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.sandbox.supportedCapabilities,
});
// Verified sync verbs grant native sync without any declaration.
expect(effective.nativeSyncIn).toBe(true);
expect(effective.nativeSyncOut).toBe(true);
// The worker did not verify the execute or reuse verbs, so the baseline denies
// the matching worker-property capabilities.
expect(effective.persistentProcessSessions).toBe(false);
expect(effective.independentControlCommands).toBe(false);
expect(effective.reusableLeases).toBe(false);
});
it("default rule two: the three opt-in fields deny by default without a declaration", () => {
// The opt-in fields are behavioral guarantees. An absent declaration denies
// them even when the worker verifies the prerequisite verb.
const effective = classifyEnvironmentCapabilities({
verifiedMethods: ["environmentExecute", "environmentSyncIn", "environmentSyncOut", "duplexChannelOpen"],
declared: null,
supportedCapabilities: ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.sandbox.supportedCapabilities,
});
expect(effective.incrementalSessionOutput).toBe(false);
expect(effective.concurrentSyncOperations).toBe(false);
expect(effective.duplexCommandStream).toBe(false);
});
it("a built-in driver static definition denies every capability it does not support", () => {
// The local and SSH drivers run no provider capability model, so their static
// support definition names no capability. Every capability resolves false even
// with every verb verified and every capability declared.
for (const driver of ["local", "ssh"] as const) {
const effective = classifyEnvironmentCapabilities({
verifiedMethods: ALL_PROVIDER_METHODS,
declared: {
reusableLeases: true,
nativeSyncIn: true,
nativeSyncOut: true,
persistentProcessSessions: true,
independentControlCommands: true,
incrementalSessionOutput: true,
concurrentSyncOperations: true,
duplexCommandStream: true,
},
supportedCapabilities: ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT[driver].supportedCapabilities,
});
for (const key of SANDBOX_CAPABILITY_KEYS) {
expect(effective[key]).toBe(false);
}
}
});
it("defines static capability support for the four drivers", () => {
expect(Object.keys(ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT).sort()).toEqual([
"local",
"plugin",
"sandbox",
"ssh",
]);
// The two remote provider drivers support the whole capability set; the two
// host drivers support none.
expect(ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.sandbox.supportedCapabilities.size).toBe(
SANDBOX_CAPABILITY_KEYS.length,
);
expect(ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.plugin.supportedCapabilities.size).toBe(
SANDBOX_CAPABILITY_KEYS.length,
);
expect(ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.local.supportedCapabilities.size).toBe(0);
expect(ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.ssh.supportedCapabilities.size).toBe(0);
});
});