From 71f459c2a5d4c7a3161d0157adef5718c7074c63 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 31 Jul 2026 07:10:09 -0700 Subject: [PATCH] fix(events): the last two live task:moved emitters carry the resolved lanes (#3135) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## The last two live emitters #3109 attached lanes at `moves.ts`. #3120 attached them at the archive and completion emits. Two live emitters were still sending `lanes: undefined`: ``` task-update.ts:962 todo -> triage update-task-deps.ts:502 emits to: task.column — the row's ACTUAL lane ``` A listener reads absence as "unknown" and falls back to `resolveTaskParkedColumnsSync`, which returns the **default board** for every task under PostgreSQL. So these two paths kept the pre-#3109 behaviour while the listener code reads as resolved at every site. `update-task-deps.ts` is the sharper of the two: it emits `to: task.column`, the row's real lane, so on a renamed board it sends e.g. `"shipped"` to a listener comparing against `"done"`. The emitter had already resolved the board and threw the answer away. Nothing errors; the branch stops firing. ## Emitter coverage after this ``` LANES moves.ts:1450 (#3109) LANES archive-lifecycle-2.ts:385 (#3120) LANES task-artifacts-ops.ts:578 (#3120) LANES task-update.ts:970 (this PR) LANES update-task-deps.ts:504 (this PR) MISSING lifecycle-ops.ts:668 deliberate — see below MISSING lifecycle-ops.ts:715 deliberate — see below ``` **Every emitter that can execute under the shipped backend now carries lanes.** ## Flagged — the two I did NOT convert `lifecycle-ops.ts:668` and `:715` stay lane-less on purpose. Both sit on the polling-replica path that file already documents as legacy-SQLite-only — it reaches `store.db`, which throws under PostgreSQL — and the same note argues against spending a signature change on dead code. I agreed rather than overrode it. If that path is ever revived they must be attached, because absence resolves to the default board rather than to nothing. ## Supersedes my own earlier PR This replaces **#3119**, which carried four emitters. #3120 landed two of them first, so I rebuilt against current `main` with only the remainder rather than resolving a conflict into a half-redundant diff. #3119 is closed with nothing lost. ## Census before / after ``` before: COLUMN guards (the backlog): 17 after: COLUMN guards (the backlog): 17 ``` Unchanged — this converts no guards. It makes the resolved answer *reach* guards that were already converted, which is the half that was missing. ## Verification Full `@fusion/core` suite **462 files / 4906 passed, 0 failed** · `test:gate` exit 0 · typecheck exit 0 · lifecycle-column census exit 0 · `pnpm lint` clean. ## Still open `main` is **red on `check:inert-sync-lanes`** and nothing in CI runs it — **#3127** fixes both halves. **#3122** restores 13 guards laundered through `mergeParkedColumns`. **#3131** corrects a 44% under-report in `--triage`. --- packages/core/src/task-store/task-update.ts | 11 ++++++++++- packages/core/src/task-store/update-task-deps.ts | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/core/src/task-store/task-update.ts b/packages/core/src/task-store/task-update.ts index c46f973ceb..2ba611eb99 100644 --- a/packages/core/src/task-store/task-update.ts +++ b/packages/core/src/task-store/task-update.ts @@ -7,6 +7,8 @@ * instance as its first parameter and performs byte-identical work. */ import {type TaskStore, storeLog} from "../store.js"; +import {toTaskMoveLanes} from "../workflow-lifecycle-traits.js"; +import {resolveWorkflowIrForTask} from "../workflow-ir-resolver.js"; import {resolveTaskLifecycleColumns} from "../workflow-lifecycle-traits.js"; import {InvalidFileScopeError} from "./errors.js"; import {mkdir, readFile, writeFile} from "node:fs/promises"; @@ -959,7 +961,14 @@ export async function updateTaskUnlockedImpl(store: TaskStore, id: string, updat } if (movedToTriage) { - store.emit("task:moved", { task, from: "todo" as Column, to: "triage" as Column, source: "engine" }); + /* FNXC:WorkflowEvents 2026-08-01-05:10 (fleet — the last two emitters): + #3109 attached lanes at moves.ts and #3120 at the archive/completion emits. This one and + `update-task-deps.ts` were still sending `lanes: undefined`, and a listener reads absence as + "unknown" and falls back to `resolveTaskParkedColumnsSync` — the DEFAULT board under + PostgreSQL. So these two paths kept the pre-#3109 behaviour while the listeners read as + resolved. */ + const lanes = toTaskMoveLanes(await resolveWorkflowIrForTask(store, id).catch(() => undefined)); + store.emit("task:moved", { task, from: "todo" as Column, to: "triage" as Column, source: "engine", lanes }); } store.emitTaskLifecycleEventSafely("task:updated", [task]); return task; diff --git a/packages/core/src/task-store/update-task-deps.ts b/packages/core/src/task-store/update-task-deps.ts index 3e961ae31d..b3bdfc8ea8 100644 --- a/packages/core/src/task-store/update-task-deps.ts +++ b/packages/core/src/task-store/update-task-deps.ts @@ -7,6 +7,8 @@ * instance as its first parameter and performs byte-identical work. */ import {TaskStore, storeLog, type TaskDependencyMutation} from "../store.js"; +import {toTaskMoveLanes} from "../workflow-lifecycle-traits.js"; +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"; @@ -504,6 +506,7 @@ export async function updateTaskDependenciesImpl(store: TaskStore, id: string, m from: respecifyFromColumn as Column, to: task.column as Column, source: "engine", + lanes: toTaskMoveLanes(await resolveWorkflowIrForTask(store, task.id).catch(() => undefined)), }); } store.emitTaskLifecycleEventSafely("task:updated", [task]);