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", () => {
|
describe("resolveLegacyStatusAdoption — every legacy (status) resumes owned", () => {
|
||||||
it("triage plan-review statuses resume the graph (writers deleted in U3)", () => {
|
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");
|
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)", () => {
|
it("live human/terminal gates are preserved (never disturbed)", () => {
|
||||||
for (const s of ["awaiting-approval", "failed", "error", "blocked", "done", "cancelled"]) {
|
for (const s of ["awaiting-approval", "failed", "error", "blocked", "done", "cancelled"]) {
|
||||||
expect(resolveLegacyStatusAdoption(s)?.kind).toBe("preserve");
|
expect(resolveLegacyStatusAdoption(s)?.kind).toBe("preserve");
|
||||||
@@ -184,7 +194,7 @@ describe("planLegacyAdoption (U9b consumers)", () => {
|
|||||||
const NOW = "2026-07-19T04:40:00.000Z";
|
const NOW = "2026-07-19T04:40:00.000Z";
|
||||||
|
|
||||||
it("clears every resume-graph status so the graph re-enters at its owning node", () => {
|
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);
|
const plan = planLegacyAdoption({ status }, NOW);
|
||||||
expect(plan.action, status).toBe("resume-graph");
|
expect(plan.action, status).toBe("resume-graph");
|
||||||
// Clearing the legacy status IS the re-entry: the graph owns the node again.
|
// 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", () => {
|
it("backfills reviewLevel-only rows and never writes both fields", () => {
|
||||||
const plan = planLegacyAdoption({ reviewLevel: 1 }, NOW);
|
const plan = planLegacyAdoption({ reviewLevel: 1 }, NOW);
|
||||||
expect(plan.patch?.enabledWorkflowSteps).toEqual([CODE_REVIEW_GROUP_ID]);
|
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):
|
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
|
- resume-graph : clear the legacy triage-owned status so the graph re-enters
|
||||||
cleanly at the owning node (planning → planning node,
|
cleanly at the owning node (planning → planning node,
|
||||||
needs-replan → plan-replan, plan-review-unavailable →
|
plan-review-unavailable → plan-review retry,
|
||||||
plan-review retry, queued/triaged → scheduler re-pickup).
|
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
|
- preserve : a live human/terminal gate the graph must NOT disturb
|
||||||
(awaiting-approval, awaiting-user-input, failed, error,
|
(awaiting-approval, awaiting-user-input, failed, error,
|
||||||
blocked, done, cancelled). Pausing is NOT a status: it is
|
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>> = {
|
export const LEGACY_STATUS_ADOPTION: Readonly<Record<string, LegacyAdoptionAction>> = {
|
||||||
// ── Triage plan-review statuses whose writers U3 deleted → graph re-entry ──
|
// ── Triage plan-review statuses whose writers U3 deleted → graph re-entry ──
|
||||||
"planning": { kind: "resume-graph", note: "re-enter planning node" },
|
"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)" },
|
"plan-review-unavailable": { kind: "resume-graph", note: "plan-review retry (leased)" },
|
||||||
// ── Scheduler / dispatch transient states → re-pickup ─────────────────────
|
// ── Scheduler / dispatch transient states → re-pickup ─────────────────────
|
||||||
"queued": { kind: "resume-graph", note: "scheduler re-queue" },
|
"queued": { kind: "resume-graph", note: "scheduler re-queue" },
|
||||||
|
|||||||
Reference in New Issue
Block a user