fix(FN-8498): stop legacy-adoption sweep from clearing live needs-replan signal
The KTD-8 startup adoption sweep mapped needs-replan to resume-graph, clearing it on every engine restart. But needs-replan is not un-migrated legacy: post-U3 it is written live by the graph's plan-replan seam and is the exact status triage's todo-rediscovery keys on to re-admit a planned todo task. Clearing it stranded replan-loop tasks in todo forever (FN-8498 sat "ready" for 80 minutes after a restart). Map it to preserve — the status is self-resuming as-is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
7
.changeset/preserve-needs-replan-adoption.md
Normal file
7
.changeset/preserve-needs-replan-adoption.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix engine restarts stranding replan-loop tasks in To Do by clearing their needs-replan signal.
|
||||
category: fix
|
||||
dev: The KTD-8 legacy-adoption table now maps `needs-replan` to `preserve` instead of `resume-graph`; it is a live graph signal written by the plan-replan seam and consumed by triage todo-rediscovery, not un-migrated legacy state.
|
||||
@@ -121,11 +121,21 @@ describe("KTD-8 adoption table — write-site census completeness (build-failing
|
||||
|
||||
describe("resolveLegacyStatusAdoption — every legacy (status) resumes owned", () => {
|
||||
it("triage plan-review statuses resume the graph (writers deleted in U3)", () => {
|
||||
for (const s of ["planning", "needs-replan", "plan-review-unavailable"]) {
|
||||
for (const s of ["planning", "plan-review-unavailable"]) {
|
||||
expect(resolveLegacyStatusAdoption(s)?.kind).toBe("resume-graph");
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
FNXC:LegacyAdoption 2026-07-22-15:55 (FN-8498 incident):
|
||||
needs-replan is written LIVE by the graph's plan-replan seam and is the exact key
|
||||
triage's todo-rediscovery uses to re-admit a planned todo task. Adoption must never
|
||||
clear it — the resume-graph mapping stranded FN-8498 in `todo` across a restart.
|
||||
*/
|
||||
it("needs-replan is preserved — it is the graph's live replan signal, not legacy", () => {
|
||||
expect(resolveLegacyStatusAdoption("needs-replan")?.kind).toBe("preserve");
|
||||
});
|
||||
|
||||
it("live human/terminal gates are preserved (never disturbed)", () => {
|
||||
for (const s of ["awaiting-approval", "failed", "error", "blocked", "done", "cancelled"]) {
|
||||
expect(resolveLegacyStatusAdoption(s)?.kind).toBe("preserve");
|
||||
@@ -184,7 +194,7 @@ describe("planLegacyAdoption (U9b consumers)", () => {
|
||||
const NOW = "2026-07-19T04:40:00.000Z";
|
||||
|
||||
it("clears every resume-graph status so the graph re-enters at its owning node", () => {
|
||||
for (const status of ["planning", "needs-replan", "plan-review-unavailable", "queued", "triaged"]) {
|
||||
for (const status of ["planning", "plan-review-unavailable", "queued", "triaged"]) {
|
||||
const plan = planLegacyAdoption({ status }, NOW);
|
||||
expect(plan.action, status).toBe("resume-graph");
|
||||
// Clearing the legacy status IS the re-entry: the graph owns the node again.
|
||||
@@ -210,6 +220,20 @@ describe("planLegacyAdoption (U9b consumers)", () => {
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
FNXC:LegacyAdoption 2026-07-22-15:55 (FN-8498 incident):
|
||||
A post-cutover todo row in the plan-replan loop carries status "needs-replan" and no
|
||||
legacyAdoptedAt stamp. The startup sweep used to clear it (resume-graph), stranding the
|
||||
task: triage's todo-rediscovery only re-admits a planned todo task on that exact status.
|
||||
The plan must skip it entirely — no status clear, no patch, no stamp — so the replan
|
||||
signal survives any number of engine restarts.
|
||||
*/
|
||||
it("survives a restart mid-replan-loop: needs-replan is skipped, never cleared (FN-8498)", () => {
|
||||
const plan = planLegacyAdoption({ status: "needs-replan" }, NOW);
|
||||
expect(plan.action).toBe("skip");
|
||||
expect(plan.patch).toBeUndefined();
|
||||
});
|
||||
|
||||
it("backfills reviewLevel-only rows and never writes both fields", () => {
|
||||
const plan = planLegacyAdoption({ reviewLevel: 1 }, NOW);
|
||||
expect(plan.patch?.enabledWorkflowSteps).toEqual([CODE_REVIEW_GROUP_ID]);
|
||||
|
||||
@@ -11,8 +11,9 @@ cutover window fails the build instead of mass-parking rows `paused` at upgrade.
|
||||
Adoption action per legacy status (KTD-8), for the FOUNDATIONAL targets (U9 scope A):
|
||||
- resume-graph : clear the legacy triage-owned status so the graph re-enters
|
||||
cleanly at the owning node (planning → planning node,
|
||||
needs-replan → plan-replan, plan-review-unavailable →
|
||||
plan-review retry, queued/triaged → scheduler re-pickup).
|
||||
plan-review-unavailable → plan-review retry,
|
||||
queued/triaged → scheduler re-pickup). NOT needs-replan —
|
||||
that is a LIVE graph signal, preserved (see its table row).
|
||||
- preserve : a live human/terminal gate the graph must NOT disturb
|
||||
(awaiting-approval, awaiting-user-input, failed, error,
|
||||
blocked, done, cancelled). Pausing is NOT a status: it is
|
||||
@@ -56,7 +57,16 @@ export interface LegacyAdoptionAction {
|
||||
export const LEGACY_STATUS_ADOPTION: Readonly<Record<string, LegacyAdoptionAction>> = {
|
||||
// ── Triage plan-review statuses whose writers U3 deleted → graph re-entry ──
|
||||
"planning": { kind: "resume-graph", note: "re-enter planning node" },
|
||||
"needs-replan": { kind: "resume-graph", note: "re-enter plan-replan node" },
|
||||
/*
|
||||
FNXC:LegacyAdoption 2026-07-22-15:55 (FN-8498 incident):
|
||||
`needs-replan` is NOT legacy — post-U3 it is written live by the graph's own plan-replan
|
||||
seam (executor.ts / scheduler.ts) and CONSUMED by triage's todo-rediscovery, which only
|
||||
re-admits a planned todo task when status === "needs-replan". The original resume-graph
|
||||
mapping cleared it on every engine restart, deleting the exact signal its consumer keys
|
||||
on and stranding replan-loop tasks in `todo` forever (FN-8498 sat "ready" for 80 minutes
|
||||
after a restart). Preserve it: the status is self-resuming — triage picks it up as-is.
|
||||
*/
|
||||
"needs-replan": { kind: "preserve", note: "live graph replan signal — triage todo-rediscovery consumes it" },
|
||||
"plan-review-unavailable": { kind: "resume-graph", note: "plan-review retry (leased)" },
|
||||
// ── Scheduler / dispatch transient states → re-pickup ─────────────────────
|
||||
"queued": { kind: "resume-graph", note: "scheduler re-queue" },
|
||||
|
||||
Reference in New Issue
Block a user