41 lines
2.1 KiB
TypeScript
41 lines
2.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { decideRecovery } from "../src/services/recoveryPolicy.js";
|
|
import { normalizeAutonomyState, retryAutonomyBlockerIfDue } from "../src/repositories/autonomyRepository.js";
|
|
|
|
describe("bounded Auto Mode recovery policy", () => {
|
|
it("walks through retry, cooldown and alternate recovery before escalation", () => {
|
|
const sequence = [1, 2, 3, 4, 5, 6].map((recoveryAttempts) => decideRecovery({
|
|
failureCount: recoveryAttempts,
|
|
recoveryAttempts,
|
|
recoveryStartedAt: "2026-09-12T10:00:00.000Z",
|
|
maxRecoveryMinutes: 120,
|
|
now: Date.parse("2026-09-12T10:10:00.000Z"),
|
|
}).stage);
|
|
expect(sequence).toEqual(["retry", "retry", "cooldown", "retry", "alternate_recovery", "escalated"]);
|
|
});
|
|
|
|
it("stops on the wall-clock budget even if attempts are low", () => {
|
|
expect(decideRecovery({
|
|
failureCount: 1,
|
|
recoveryAttempts: 1,
|
|
recoveryStartedAt: "2026-09-12T08:00:00.000Z",
|
|
maxRecoveryMinutes: 30,
|
|
now: Date.parse("2026-09-12T08:31:00.000Z"),
|
|
})).toMatchObject({ stage: "escalated", exhausted: true, reason: "recovery_wall_clock_limit" });
|
|
});
|
|
|
|
it("escalates a persisted blocker when its cooldown wakes after the wall-clock budget", () => {
|
|
const state = normalizeAutonomyState({
|
|
enabled: true,
|
|
maxRecoveryMinutes: 30,
|
|
recoveryStartedAt: "2026-09-12T08:00:00.000Z",
|
|
blocker: { at: "2026-09-12T08:00:00.000Z", phaseId: "phase-1", reason: "Runner unavailable", kind: "retryable", retryAt: "2026-09-12T08:31:00.000Z", recoveryStage: "retry", recoveryAttempts: 1, recoveryStartedAt: "2026-09-12T08:00:00.000Z" },
|
|
roadmap: { id: "roadmap-1", title: "Release", status: "blocked", currentPhaseIndex: 0, importedAt: "2026-09-12T08:00:00.000Z", phases: [{ id: "phase-1", title: "Build", objective: "Build", status: "blocked", items: [] }] },
|
|
});
|
|
const result = retryAutonomyBlockerIfDue(state, Date.parse("2026-09-12T08:31:00.000Z"));
|
|
expect(result.blocker).toMatchObject({ recoveryStage: "escalated" });
|
|
expect(result.status).toBe("blocked");
|
|
expect(result.roadmap?.status).toBe("blocked");
|
|
});
|
|
});
|