feat(FN-4545): complete Step 1 — extend auto-recovery failure taxonomy

Fusion-Task-Id: FN-4545
Fusion-Task-Lineage: 95108429-618e-4a6d-9fa9-7ac2596665a2
This commit is contained in:
Fusion
2026-05-14 20:22:18 -07:00
committed by gsxdsm
parent 502a68948d
commit ecd5ebfc33
2 changed files with 19 additions and 1 deletions

View File

@@ -234,7 +234,8 @@ export type AutoRecoveryFailureClass =
| "branch-cross-contamination" | "branch-cross-contamination"
| "branch-conflict-tripwire" | "branch-conflict-tripwire"
| "branch-conflict-recovery-exhausted" | "branch-conflict-recovery-exhausted"
| "branch-conflict-unrecoverable"; | "branch-conflict-unrecoverable"
| "message-delivery-failure";
export interface AutoRecoverySettings { export interface AutoRecoverySettings {
mode: AutoRecoveryMode; mode: AutoRecoveryMode;
@@ -265,6 +266,7 @@ export function normalizeAutoRecovery(value: unknown): AutoRecoverySettings {
"branch-conflict-tripwire", "branch-conflict-tripwire",
"branch-conflict-recovery-exhausted", "branch-conflict-recovery-exhausted",
"branch-conflict-unrecoverable", "branch-conflict-unrecoverable",
"message-delivery-failure",
].includes(k) ].includes(k)
&& typeof v === "string" && typeof v === "string"
&& (AUTO_RECOVERY_MODES as readonly string[]).includes(v) && (AUTO_RECOVERY_MODES as readonly string[]).includes(v)

View File

@@ -20,6 +20,7 @@ const classes: AutoRecoveryFailure["class"][] = [
"branch-conflict-tripwire", "branch-conflict-tripwire",
"branch-conflict-recovery-exhausted", "branch-conflict-recovery-exhausted",
"branch-conflict-unrecoverable", "branch-conflict-unrecoverable",
"message-delivery-failure",
]; ];
describe("auto-recovery dispatcher", () => { describe("auto-recovery dispatcher", () => {
@@ -68,6 +69,21 @@ describe("auto-recovery dispatcher", () => {
expect(decision.rationale).toBe("destructive-ambiguity"); expect(decision.rationale).toBe("destructive-ambiguity");
}); });
it.each([
["off", "pause"],
["deterministic-only", "pause"],
["programmatic", "retry"],
["ai-assisted", "retry"],
] as const)("message-delivery-failure routes %s to %s", (mode, expectedAction) => {
const { dispatcher } = createDispatcher();
const decision = dispatcher.classify({ class: "message-delivery-failure", taskId: "FN-1", pausedReason: "message-delivery-failure" }, {
task,
retryCount: 0,
settings: { mode, maxRetries: 3 },
});
expect(decision.action).toBe(expectedAction);
});
it("dispatch falls back to pause when handler missing", async () => { it("dispatch falls back to pause when handler missing", async () => {
const { dispatcher, database } = createDispatcher(); const { dispatcher, database } = createDispatcher();
const decision = await dispatcher.dispatch({ class: "branch-conflict-unrecoverable", taskId: "FN-1", pausedReason: "branch-conflict-unrecoverable" }, { const decision = await dispatcher.dispatch({ class: "branch-conflict-unrecoverable", taskId: "FN-1", pausedReason: "branch-conflict-unrecoverable" }, {