mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-28 04:02:18 +01:00
fix: reject incompatible pinned workspaces
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
79efb23338
commit
4f26f3f7b8
2 files changed
+67
-23
No files matched your search
@@ -26,6 +26,8 @@ import {
|
||||
parseSessionCompactionPolicy,
|
||||
PINNED_EXECUTION_WORKSPACE_RESTORE_FAILED_ERROR_CODE,
|
||||
PINNED_EXECUTION_WORKSPACE_UNAVAILABLE_ERROR_CODE,
|
||||
PINNED_EXECUTION_WORKSPACE_INCOMPATIBLE_ERROR_CODE,
|
||||
ExecutionWorkspaceReuseFailure,
|
||||
provisionExecutionWorkspaceForFreshnessDecision,
|
||||
reconcileReusedExecutionWorkspaceProjectWorkspaceId,
|
||||
resolveExecutionWorkspaceConfigFreshness,
|
||||
@@ -41,7 +43,7 @@ import {
|
||||
stripHostWorkspaceProvisionForLowTrustSandbox,
|
||||
stripWorkspaceRuntimeFromExecutionRunConfig,
|
||||
shouldResetTaskSessionForModelChange,
|
||||
shouldReusePinnedExecutionWorkspaceForRun,
|
||||
isPinnedExecutionWorkspaceIncompatibleWithTrust,
|
||||
stripConfiguredModelFromSessionParams,
|
||||
stripPaperclipSessionMetadataFromSessionParams,
|
||||
normalizeSessionParams,
|
||||
@@ -1661,31 +1663,58 @@ describe("effective run execution workspace config freshness", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("does not reuse an available shared pin for a low-trust isolated run", () => {
|
||||
expect(shouldReusePinnedExecutionWorkspaceForRun({
|
||||
it("marks an available shared pin incompatible with a low-trust isolated run", () => {
|
||||
expect(isPinnedExecutionWorkspaceIncompatibleWithTrust({
|
||||
requestedShouldReuseExisting: true,
|
||||
trustPresetKind: "low_trust_review",
|
||||
requestedExecutionWorkspaceMode: "isolated_workspace",
|
||||
existingExecutionWorkspaceMode: "shared_workspace",
|
||||
})).toBe(false);
|
||||
})).toBe(true);
|
||||
});
|
||||
|
||||
it("reuses an available isolated pin for a low-trust isolated run", () => {
|
||||
expect(shouldReusePinnedExecutionWorkspaceForRun({
|
||||
it("accepts an available isolated pin for a low-trust isolated run", () => {
|
||||
expect(isPinnedExecutionWorkspaceIncompatibleWithTrust({
|
||||
requestedShouldReuseExisting: true,
|
||||
trustPresetKind: "low_trust_review",
|
||||
requestedExecutionWorkspaceMode: "isolated_workspace",
|
||||
existingExecutionWorkspaceMode: "isolated_workspace",
|
||||
})).toBe(true);
|
||||
})).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps unavailable low-trust pins on the named reuse-failure path", () => {
|
||||
expect(shouldReusePinnedExecutionWorkspaceForRun({
|
||||
it("keeps unavailable low-trust pins on the named availability-failure path", () => {
|
||||
expect(isPinnedExecutionWorkspaceIncompatibleWithTrust({
|
||||
requestedShouldReuseExisting: true,
|
||||
trustPresetKind: "low_trust_review",
|
||||
requestedExecutionWorkspaceMode: "isolated_workspace",
|
||||
existingExecutionWorkspaceMode: null,
|
||||
})).toBe(true);
|
||||
})).toBe(false);
|
||||
});
|
||||
|
||||
it("reports a named incompatibility without realizing a replacement workspace", () => {
|
||||
const metadata = buildWorkspaceConfigMetadata();
|
||||
const workspaceConfigFreshness = resolveExecutionWorkspaceConfigFreshness({
|
||||
hasExistingWorkspace: true,
|
||||
existingWorkspaceMetadata: persistedWorkspaceConfigFingerprint(metadata),
|
||||
nextMetadata: metadata,
|
||||
});
|
||||
const failure = new ExecutionWorkspaceReuseFailure({
|
||||
reason: PINNED_EXECUTION_WORKSPACE_INCOMPATIBLE_ERROR_CODE,
|
||||
issueRef: { id: "issue-1", identifier: "PAP-42" },
|
||||
runId: "run-1",
|
||||
executionWorkspaceId: "workspace-shared",
|
||||
workspaceConfigFreshness,
|
||||
});
|
||||
|
||||
expect(failure).toMatchObject({
|
||||
code: PINNED_EXECUTION_WORKSPACE_INCOMPATIBLE_ERROR_CODE,
|
||||
message: expect.stringContaining("incompatible with the run's isolation policy"),
|
||||
resultJson: {
|
||||
executionWorkspaceReuse: expect.objectContaining({
|
||||
reason: PINNED_EXECUTION_WORKSPACE_INCOMPATIBLE_ERROR_CODE,
|
||||
executionWorkspaceId: "workspace-shared",
|
||||
}),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -398,6 +398,8 @@ export const PINNED_EXECUTION_WORKSPACE_RESTORE_FAILED_ERROR_CODE =
|
||||
"pinned_execution_workspace_restore_failed";
|
||||
export const PINNED_EXECUTION_WORKSPACE_UNAVAILABLE_ERROR_CODE =
|
||||
"pinned_execution_workspace_unavailable";
|
||||
export const PINNED_EXECUTION_WORKSPACE_INCOMPATIBLE_ERROR_CODE =
|
||||
"pinned_execution_workspace_incompatible";
|
||||
const EXECUTION_REVIEW_PARTICIPANT_RECOVERY_RETRY_REASON = "execution_review_participant_recovery";
|
||||
const EXECUTION_REVIEW_PARTICIPANT_RECOVERY_WAKE_REASON = "execution_review_participant_recovery";
|
||||
const EXECUTION_REVIEW_PARTICIPANT_RECOVERY_CAUSE = "execution_review_participant_recovery";
|
||||
@@ -4355,7 +4357,7 @@ export function resolveExecutionWorkspaceReuseRequestForIssue(input: {
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldReusePinnedExecutionWorkspaceForRun(input: {
|
||||
export function isPinnedExecutionWorkspaceIncompatibleWithTrust(input: {
|
||||
requestedShouldReuseExisting: boolean;
|
||||
trustPresetKind: TrustPresetResolution["kind"];
|
||||
requestedExecutionWorkspaceMode: ReturnType<typeof resolveExecutionWorkspaceMode>;
|
||||
@@ -4363,10 +4365,7 @@ export function shouldReusePinnedExecutionWorkspaceForRun(input: {
|
||||
}): boolean {
|
||||
if (!input.requestedShouldReuseExisting) return false;
|
||||
|
||||
// A durable pin cannot weaken the low-trust isolation boundary. An available
|
||||
// non-isolated pin is bypassed so the policy can provision a fresh isolated workspace.
|
||||
// Missing or archived pins still flow through the named reuse-failure path.
|
||||
return !(
|
||||
return (
|
||||
input.trustPresetKind === "low_trust_review"
|
||||
&& input.requestedExecutionWorkspaceMode === "isolated_workspace"
|
||||
&& input.existingExecutionWorkspaceMode !== null
|
||||
@@ -4394,7 +4393,8 @@ export function resolveExecutionWorkspaceReuseProvisioningPolicy(input: {
|
||||
|
||||
type ExecutionWorkspaceReuseFailureReason =
|
||||
| typeof PINNED_EXECUTION_WORKSPACE_RESTORE_FAILED_ERROR_CODE
|
||||
| typeof PINNED_EXECUTION_WORKSPACE_UNAVAILABLE_ERROR_CODE;
|
||||
| typeof PINNED_EXECUTION_WORKSPACE_UNAVAILABLE_ERROR_CODE
|
||||
| typeof PINNED_EXECUTION_WORKSPACE_INCOMPATIBLE_ERROR_CODE;
|
||||
|
||||
function formatPinnedExecutionWorkspaceReuseFailure(input: {
|
||||
reason: ExecutionWorkspaceReuseFailureReason;
|
||||
@@ -4411,12 +4411,17 @@ function formatPinnedExecutionWorkspaceReuseFailure(input: {
|
||||
: input.cause != null
|
||||
? String(input.cause)
|
||||
: null;
|
||||
const incompatible = input.reason === PINNED_EXECUTION_WORKSPACE_INCOMPATIBLE_ERROR_CODE;
|
||||
const remediation = input.reason === PINNED_EXECUTION_WORKSPACE_RESTORE_FAILED_ERROR_CODE
|
||||
? "Inspect the pinned execution workspace restore/provision logs, repair or unarchive the workspace, or intentionally clear the issue's execution workspace binding before retrying."
|
||||
: "Repair or unarchive the pinned execution workspace, or intentionally clear the issue's execution workspace binding before retrying.";
|
||||
const message = causeMessage
|
||||
? `Issue ${issueLabel} pinned execution workspace ${workspaceLabel}, but the workspace could not be restored because ${causeMessage}.`
|
||||
: `Issue ${issueLabel} pinned execution workspace ${workspaceLabel}, but the workspace could not be restored.`;
|
||||
: incompatible
|
||||
? "Pin an isolated workspace, change the task trust policy, or intentionally clear the issue's execution workspace binding before retrying."
|
||||
: "Repair or unarchive the pinned execution workspace, or intentionally clear the issue's execution workspace binding before retrying.";
|
||||
const message = incompatible
|
||||
? `Issue ${issueLabel} pinned execution workspace ${workspaceLabel}, but that workspace is incompatible with the run's isolation policy.`
|
||||
: causeMessage
|
||||
? `Issue ${issueLabel} pinned execution workspace ${workspaceLabel}, but the workspace could not be restored because ${causeMessage}.`
|
||||
: `Issue ${issueLabel} pinned execution workspace ${workspaceLabel}, but the workspace could not be restored.`;
|
||||
|
||||
return `${message} ${remediation}`;
|
||||
}
|
||||
@@ -14039,7 +14044,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
||||
issueExecutionWorkspacePreference: issueRef?.executionWorkspacePreference ?? null,
|
||||
existingExecutionWorkspaceStatus: existingExecutionWorkspace?.status ?? null,
|
||||
});
|
||||
const requestedShouldReuseExisting = shouldReusePinnedExecutionWorkspaceForRun({
|
||||
const pinnedExecutionWorkspaceIncompatibleWithTrust = isPinnedExecutionWorkspaceIncompatibleWithTrust({
|
||||
requestedShouldReuseExisting: workspaceReuseRequest.requestedShouldReuseExisting,
|
||||
trustPresetKind: trustPreset.kind,
|
||||
requestedExecutionWorkspaceMode,
|
||||
@@ -14047,13 +14052,14 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
||||
? existingExecutionWorkspace?.mode ?? null
|
||||
: null,
|
||||
});
|
||||
const reusableExistingExecutionWorkspace = requestedShouldReuseExisting
|
||||
&& workspaceReuseRequest.existingExecutionWorkspaceAvailable
|
||||
const requestedShouldReuseExisting = workspaceReuseRequest.requestedShouldReuseExisting;
|
||||
const reusableExistingExecutionWorkspace = workspaceReuseRequest.existingExecutionWorkspaceAvailable
|
||||
? existingExecutionWorkspace
|
||||
: null;
|
||||
if (
|
||||
requestedShouldReuseExisting &&
|
||||
reusableExistingExecutionWorkspace &&
|
||||
!pinnedExecutionWorkspaceIncompatibleWithTrust &&
|
||||
(
|
||||
issueExecutionWorkspaceSettings?.mode == null ||
|
||||
issueExecutionWorkspaceSettings.mode === "inherit" ||
|
||||
@@ -14554,6 +14560,15 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
||||
inferredMetadata: inferredExistingWorkspaceConfigMetadata,
|
||||
nextMetadata: latestWorkspaceConfigMetadata,
|
||||
});
|
||||
if (pinnedExecutionWorkspaceIncompatibleWithTrust) {
|
||||
throw new ExecutionWorkspaceReuseFailure({
|
||||
reason: PINNED_EXECUTION_WORKSPACE_INCOMPATIBLE_ERROR_CODE,
|
||||
issueRef,
|
||||
runId: run.id,
|
||||
executionWorkspaceId: workspaceReuseRequest.requestedExecutionWorkspaceId,
|
||||
workspaceConfigFreshness,
|
||||
});
|
||||
}
|
||||
const workspaceReuseProvisioningPolicy = resolveExecutionWorkspaceReuseProvisioningPolicy({
|
||||
requestedShouldReuseExisting,
|
||||
workspaceConfigFreshness,
|
||||
|
||||
Reference in new issue
Block a user