Files
fusion/packages/engine/src/__tests__/execution-block-classifier.test.ts
gsxdsm 1e7f510ee2 fix: stop blocking tasks on open-PR file claims — board tasks are the only blockers
Remove the FN-8700 PR/file-claim blocking mechanism end to end (operator
decision after FN-8728 parked on unrelated PR #2398):

- Drop the AGENTS.md claim-check rule and scripts/check-file-claimed.mjs
- Executor prompt + fn_task_done no longer accept pr:N refs or treat open
  PRs as blocked-exit reasons
- execution-block-classifier classifies on Fusion task dependencies only;
  legacy pr refs are discarded, reason prose never makes a block durable
- Remove the session-log BLOCKED promotion and the gh-backed
  reconcile-external-pr-blockers self-healing sweep
- Legacy file-claim parks are no longer honored, so previously PR-blocked
  rows recover via normal paths

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:06:38 -07:00

99 lines
3.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
BLOCKED_THRASH_LIMIT,
classifyBlockedExit,
countBlockedThrashHits,
isDurableBlockedTask,
partitionBlockedByRefs,
} from "../execution-block-classifier.js";
/*
FNXC:HonestBlockedExit 2026-08-02-23:59 (operator decision — FN-8728 vs PR #2398):
Blocked exits classify on Fusion task dependencies ONLY. PR refs and file-claim
reason language must never produce a durable park — open PRs are not blockers.
*/
describe("partitionBlockedByRefs", () => {
it("keeps task ids and discards legacy pr refs and junk", () => {
expect(partitionBlockedByRefs(["FN-8145", "pr:2398", "#2400", "PR-12", " ", "fn-8145"])).toEqual({
taskIds: ["FN-8145"],
});
});
});
describe("classifyBlockedExit", () => {
it("allows auto-replan for empty blockers regardless of reason prose", () => {
const c = classifyBlockedExit("requirements contradict each other", []);
expect(c.allowAutoReplan).toBe(true);
expect(c.class).toBe("plan-defect");
});
it("ignores file-claim / PR language — reason prose never makes a block durable", () => {
const reason =
"Required SQL finding packages/core/src/task-store/reads.ts:619 is actively claimed by PR #2398. " +
"check-file-claimed reports collision policy.";
const c = classifyBlockedExit(reason, []);
expect(c.allowAutoReplan).toBe(true);
expect(c.class).toBe("plan-defect");
});
it("rejects auto-replan when blockedBy carries task deps", () => {
const c = classifyBlockedExit("waiting on upstream", ["FN-8145"]);
expect(c.allowAutoReplan).toBe(false);
expect(c.class).toBe("external");
expect(c.thrashSignature).toBe("tasks:FN-8145");
});
it("discards pr: refs in blockedBy — a PR-only block is a plan defect", () => {
const c = classifyBlockedExit("files claimed", ["pr:2398"]);
expect(c.allowAutoReplan).toBe(true);
expect(c.class).toBe("plan-defect");
});
});
describe("isDurableBlockedTask", () => {
it("honors only metadata-classed external (task-dependency) parks", () => {
expect(
isDurableBlockedTask({
status: "failed",
error: "BLOCKED: waiting on FN-8145",
sourceMetadata: { blockedClass: "external" },
}),
).toBe(true);
// Legacy FN-8700 file-claim parks are deliberately NOT durable anymore.
expect(
isDurableBlockedTask({
status: "failed",
error: "BLOCKED: actively claimed by PR #2398",
sourceMetadata: {
blockedClass: "file-claim",
externalBlockers: [{ kind: "github-pr", number: 2398 }],
},
}),
).toBe(false);
expect(
isDurableBlockedTask({ status: "failed", error: "BLOCKED: actively claimed by PR #2398" }),
).toBe(false);
});
});
describe("countBlockedThrashHits", () => {
it("counts recent BLOCKED log rows matching the task-dependency signature", () => {
const now = Date.parse("2026-08-02T01:30:00.000Z");
const log = [
{ action: "BLOCKED: waiting on FN-8145", timestamp: "2026-08-02T01:00:00.000Z" },
{ action: "BLOCKED: still waiting on FN-8145", timestamp: "2026-08-02T01:10:00.000Z" },
{ action: "BLOCKED: waiting on FN-8145", timestamp: "2026-08-02T01:20:00.000Z" },
{ action: "unrelated progress", timestamp: "2026-08-02T01:25:00.000Z" },
];
const sig = classifyBlockedExit("waiting on upstream", ["FN-8145"]).thrashSignature;
expect(countBlockedThrashHits(log, sig, now)).toBeGreaterThanOrEqual(BLOCKED_THRASH_LIMIT);
});
it("never counts hits for the plan-defect signature", () => {
const now = Date.parse("2026-08-02T01:30:00.000Z");
const log = [{ action: "BLOCKED: anything", timestamp: "2026-08-02T01:20:00.000Z" }];
expect(countBlockedThrashHits(log, "plan-defect", now)).toBe(0);
});
});