fix(events): the last two live task:moved emitters carry the resolved lanes (#3135)

## 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`.
This commit is contained in:
gsxdsm
2026-07-31 07:10:09 -07:00
committed by GitHub
parent f818bdce2c
commit 71f459c2a5
2 changed files with 13 additions and 1 deletions

View File

@@ -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;

View File

@@ -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]);