mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 16:27:18 +02:00
Make task recovery durable and preserve the latest user request across native and legacy continuations. Keep routine recovery quiet and prevent replay when action outcomes are uncertain. Co-Authored-By: Paperclip <noreply@paperclip.ing>
78 lines
2.4 KiB
TypeScript
78 lines
2.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
decideNativeReplacement,
|
|
type NativeReplacementEvidence,
|
|
} from "./native-replacement-evidence.js";
|
|
const safe: NativeReplacementEvidence = {
|
|
failedSession: true,
|
|
failureMeaningKnown: true,
|
|
predecessorFenced: true,
|
|
providerStopped: true,
|
|
workspacePreserved: true,
|
|
historyComplete: true,
|
|
effectInventoryComplete: true,
|
|
attempts: 1,
|
|
invocations: [],
|
|
apiReceipts: {},
|
|
uncertainProviderActions: [],
|
|
};
|
|
describe("evidence-based replacement", () => {
|
|
it("allows a fenced read-only continuation within the shared incident budget", () =>
|
|
expect(decideNativeReplacement(safe)).toEqual({
|
|
allowed: true,
|
|
remainingAttempts: 2,
|
|
}));
|
|
it.each([
|
|
[{ failureMeaningKnown: false }, "provider_failure_meaning_unverified"],
|
|
[{ attempts: 3 }, "execution_recovery_budget_exhausted"],
|
|
[{ predecessorFenced: false }, "provider_ownership_unverified"],
|
|
[{ providerStopped: false }, "provider_ownership_unverified"],
|
|
[{ workspacePreserved: false }, "continuation_evidence_incomplete"],
|
|
[
|
|
{ effectInventoryComplete: false },
|
|
"provider_effect_inventory_unavailable",
|
|
],
|
|
[{ historyComplete: false }, "continuation_evidence_incomplete"],
|
|
[
|
|
{ apiReceipts: { write: { state: "pending" } } },
|
|
"uncertain_control_plane_action",
|
|
],
|
|
[
|
|
{ uncertainProviderActions: ["shell:send-email"] },
|
|
"uncertain_provider_action",
|
|
],
|
|
] as const)("rejects %j with a specific next action", (change, cause) =>
|
|
expect(
|
|
decideNativeReplacement({
|
|
...safe,
|
|
...change,
|
|
uncertainProviderActions:
|
|
"uncertainProviderActions" in change
|
|
? [...change.uncertainProviderActions]
|
|
: [],
|
|
}),
|
|
).toMatchObject({ allowed: false, cause, nextAction: expect.any(String) }),
|
|
);
|
|
it("names the uncertain write instead of granting a generic retry", () => {
|
|
expect(
|
|
decideNativeReplacement({
|
|
...safe,
|
|
invocations: [
|
|
{
|
|
id: "email-1",
|
|
toolName: "send_email",
|
|
riskLevel: "write",
|
|
status: "running",
|
|
completedAt: null,
|
|
resultHash: null,
|
|
},
|
|
],
|
|
}),
|
|
).toMatchObject({
|
|
allowed: false,
|
|
cause: "uncertain_external_action",
|
|
nextAction: expect.stringContaining("email-1"),
|
|
});
|
|
});
|
|
});
|