54 lines
2.4 KiB
TypeScript
54 lines
2.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { ISSUE_RECOVERY_ACTION_KINDS } from "@paperclipai/shared";
|
|
import { classifyContinuationFailure } from "../services/recovery/service.ts";
|
|
import {
|
|
DISPOSITION_REPAIR_BASE_DELAYS_MS,
|
|
DISPOSITION_REPAIR_MAX_ATTEMPTS,
|
|
dispositionRepairDelayMs,
|
|
} from "../services/recovery/disposition-repair.ts";
|
|
|
|
const deliberateWaitRun = {
|
|
id: "run-1",
|
|
agentId: "agent-1",
|
|
status: "cancelled" as const,
|
|
error: "Continuation parked",
|
|
errorCode: "issue_continuation_waiting_on_review",
|
|
contextSnapshot: {},
|
|
livenessState: null,
|
|
startedAt: new Date("2026-08-11T00:00:00.000Z"),
|
|
createdAt: new Date("2026-08-11T00:00:00.000Z"),
|
|
};
|
|
|
|
describe("owner-sticky disposition repair", () => {
|
|
it("classifies a deliberate wait without a target into its dedicated bounded lane", () => {
|
|
expect(ISSUE_RECOVERY_ACTION_KINDS).toContain("deliberate_wait_without_target");
|
|
expect(classifyContinuationFailure(deliberateWaitRun)).toEqual({
|
|
kind: "deliberate_wait_without_target",
|
|
maxAttempts: 5,
|
|
baseBackoffMs: 60_000,
|
|
errorCode: "issue_continuation_waiting_on_review",
|
|
});
|
|
});
|
|
|
|
it("uses the persisted five-attempt owner-sticky schedule with bounded deterministic jitter", () => {
|
|
const fingerprint = "disposition_repair:v1:example";
|
|
const timings = [1, 2, 3, 4, 5].map((attempt) => dispositionRepairDelayMs(attempt, fingerprint));
|
|
|
|
expect(DISPOSITION_REPAIR_MAX_ATTEMPTS).toBe(5);
|
|
expect(DISPOSITION_REPAIR_BASE_DELAYS_MS).toEqual([0, 60_000, 120_000, 240_000, 480_000]);
|
|
expect(timings[0]).toEqual({ baseDelayMs: 0, jitterMs: 0, delayMs: 0 });
|
|
expect(timings[1]?.baseDelayMs).toBe(60_000);
|
|
expect(timings[1]?.jitterMs).toBeGreaterThanOrEqual(0);
|
|
expect(timings[1]?.jitterMs).toBeLessThanOrEqual(6_000);
|
|
expect(timings[2]?.baseDelayMs).toBe(120_000);
|
|
expect(timings[2]?.jitterMs).toBeGreaterThanOrEqual(0);
|
|
expect(timings[2]?.jitterMs).toBeLessThanOrEqual(12_000);
|
|
expect(timings[3]?.baseDelayMs).toBe(240_000);
|
|
expect(timings[3]?.jitterMs).toBeLessThanOrEqual(24_000);
|
|
expect(timings[4]?.baseDelayMs).toBe(480_000);
|
|
expect(timings[4]?.jitterMs).toBeLessThanOrEqual(48_000);
|
|
expect(dispositionRepairDelayMs(2, fingerprint)).toEqual(timings[1]);
|
|
expect(() => dispositionRepairDelayMs(6, fingerprint)).toThrow(/Invalid disposition repair attempt/);
|
|
});
|
|
});
|