fix(core): refine + duplicate create into the resolved intake lane, not the deleted triage column

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-31 15:08:20 -07:00
parent 3916e062aa
commit a20ddf6ed6
5 changed files with 39 additions and 4 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Refined and duplicated tasks land in the workflow's Planning column instead of a deleted legacy column.
category: fix
dev: task_refine (update-task-deps.ts) and task_duplicate (project-store-ops.ts) hardcoded column "triage"; both now resolve resolveWorkflowIntakeFacts().intake with the literal as last resort. Symptom: amber PLANNING badge (badge color keys off raw column id) on cards in an undeclared column.

View File

@@ -43,7 +43,9 @@ pgDescribe("refineTask / duplicateTask backend mode (PostgreSQL)", () => {
expect(refined.id).not.toBe(source.id);
expect(refined.sourceType).toBe("task_refine");
expect(refined.sourceParentTaskId).toBe(source.id);
expect(refined.column).toBe("triage");
// FNXC:MergedPlanningColumn 2026-07-31-22:40: refine resolves the workflow intake lane; the
// default coding workflow's intake is the merged `todo` Planning column ("triage" is deleted).
expect(refined.column).toBe("todo");
expect(refined.dependencies).toEqual([source.id]);
expect(refined.description).toContain("Please tighten the empty-state copy");

View File

@@ -10,6 +10,7 @@
*/
import {TaskStore, storeLog, WORKFLOW_COMPILED_STEP_TEMPLATE_PREFIX, WORKFLOW_MOVE_POLICY_TIMEOUT_MS} from "../store.js";
import { resolveCapacityPoolId } from "../workflow-capacity.js";
import {resolveWorkflowIntakeFacts} from "./task-creation.js";
import {TransitionRejectionError} from "./errors.js";
import * as schema from "../postgres/schema/index.js";
import {and, eq, isNull, ne, or, sql} from "drizzle-orm";
@@ -208,7 +209,12 @@ export async function duplicateTaskImpl(store: TaskStore, id: string): Promise<T
title: normalizedTitle.title ?? undefined,
description: `${sourceTask.description}\n\n(Duplicated from ${id})`,
priority: normalizeTaskPriority(sourceTask.priority),
column: "triage",
/*
FNXC:MergedPlanningColumn 2026-07-31-22:35 (missed creation surface — duplicate):
Same fix as refine: resolve the default workflow's intake lane instead of the legacy
`"triage"` literal, which the merged coding workflow no longer declares.
*/
column: ((await resolveWorkflowIntakeFacts(store)).intake ?? "triage") as Task["column"],
modelPresetId: sourceTask.modelPresetId,
sourceType: "task_duplicate",
sourceParentTaskId: id,

View File

@@ -100,7 +100,17 @@ directly and survives any rename or merge.
Unresolvable workflow returns `{ manual: false }` with no intake, so callers keep their existing
conservative behavior rather than acting on a guess.
*/
async function resolveWorkflowIntakeFacts(
/*
FNXC:MergedPlanningColumn 2026-07-31-22:30 (missed creation surfaces):
Exported so refine (`update-task-deps.ts`) and duplicate (`project-store-ops.ts`) resolve the same
intake lane as the main create. Both built Task rows directly inside `createTaskWithId` callbacks
with a hardcoded `column: "triage"` — a column the merged coding workflow no longer declares — so a
refined/duplicated card landed in an UNDECLARED column: rendered with the legacy amber badge,
invisible to trait-driven sweeps until the undeclared-column re-home rescued it. #2589/#2603 fixed
the main create; these two surfaces were the enumeration gap (found via a two-tone Planning badge on
the live board).
*/
export async function resolveWorkflowIntakeFacts(
store: TaskStore,
workflowIdOverride?: string,
): Promise<{ intake?: string; hold?: string; manual: boolean }> {

View File

@@ -12,6 +12,7 @@ import {resolveWorkflowIrForTask} from "../workflow-ir-resolver.js";
import {buildRefinementSeedPrompt} from "../mesh-task-replication.js";
import {SelfDefeatingDependencyError, detectSelfDefeatingDependency} from "./errors.js";
import {resolveTaskLifecycleColumns} from "../workflow-lifecycle-traits.js";
import {resolveWorkflowIntakeFacts} from "./task-creation.js";
import type {WorkflowIr} from "../workflow-ir-types.js";
import {mkdir, readFile, writeFile} from "node:fs/promises";
import {join} from "node:path";
@@ -111,6 +112,15 @@ export async function refineTaskImpl(store: TaskStore, id: string, feedback: str
});
}
/*
FNXC:MergedPlanningColumn 2026-07-31-22:35 (missed creation surface — refine):
Resolve the inherited workflow's intake lane instead of the legacy `"triage"` literal. The
hardcoded id landed refinements in a column the merged coding workflow no longer declares —
surfaced on the live board as an amber PLANNING badge (badge color keys off the raw column id)
on a card invisible to trait-driven sweeps until the undeclared-column re-home. Literal survives
only as the last resort for a store that cannot resolve any workflow, matching createTask.
*/
const refineIntakeColumn = (await resolveWorkflowIntakeFacts(store, pendingWorkflowSelection?.workflowId)).intake ?? "triage";
const newTask = await store.createTaskWithDistributedReservation({ description: feedback.trim() }, {
createTaskWithId: async (newId) => {
// FN-5077: keep deterministic "Refinement" fallback when normalized refinement label is unusable (null).
@@ -138,7 +148,7 @@ export async function refineTaskImpl(store: TaskStore, id: string, feedback: str
title: normalizedTitle.title ?? "Refinement",
description: `${feedback.trim()}\n\nRefines: ${id}`,
priority: normalizeTaskPriority(sourceTask.priority),
column: "triage",
column: refineIntakeColumn as Task["column"],
dependencies: [id],
sourceType: "task_refine",
sourceParentTaskId: id,