Files
fusion/packages
gsxdsm ea477f3ada fix(core): team analytics reported an idle project on a renamed board (#2864)
First of the 14 genuinely lane-bound SQL sites from #2839. I had been
deferring these as "the owner is active in those files" — then checked,
and **no open PR touches them**. The batch-core commit had already
landed, so there was nothing in flight to collide with. The deferral was
an assumption I could have tested two turns earlier.

## The defect

`aggregateTeamAnalytics` filtered in SQL on `"column" = 'done'` and `IN
('in-progress','in-review')`. On a board whose lanes are renamed those
match nothing, so per-agent completed counts, the project total, and the
in-flight breakdown all came back **zero**.

Nothing errors. A dashboard reading *"0 tasks completed"* for a team
that shipped all week looks like an idle project, not a bug — which is
why this survives review and never gets filed.

**Why the sweep missed it:** the lifecycle census parses TypeScript
comparisons; these ids live inside SQL strings, which are string data.
The batch-core conversion fixed this file's TS guards **today** and left
the queries untouched. The file scored as converted.

## Resolved per project, not per task

`resolveProjectColumnsForRoles` gives the union of a role's columns
across the project's workflows, which is the right set here because
analytics aggregates a whole project — so a bound `IN` list is
sufficient. The merge-queue cleanup needed the
*superset-then-decide-in-JS* shape instead, because its lanes are
genuinely per task and SQL cannot know a task's workflow. Same program,
two correct answers; worth not copying the wrong one.

## Measured

Reverted, exactly one case flips:

```
✓ default vocabulary: a completed task is counted
× renamed vocabulary: a task in the RENAMED complete lane is counted
✓ renamed vocabulary: a task in the WIP lane is NOT counted as completed
✓ without a lane store, the legacy ids still answer
  Tests  1 failed | 3 passed (4)
```

The three controls are deliberate: the default vocabulary (a generally
broken aggregator cannot hide behind the renamed case), a WIP task that
must **not** count as completed on the renamed board (resolving real
lanes must not degrade into "every column counts" — an undercount turned
overcount is harder to notice), and an omitted lane store that must keep
the legacy answer byte-identical.

## Two mistakes the first attempt made, both caught by running it

- **`= ANY(${array})` does not work.** Drizzle expands a JS array in a
template into a comma tuple, so PostgreSQL rejected `(($1,$2,$3))` with
*"op ANY/ALL (array) requires array on right side"*. An `IN` list of
individual bound parameters is the working shape. Each id stays a
parameter — these come from operator-authored workflow definitions and
are never interpolated as SQL text.
- The fixture's `ON CONFLICT (id)` had no matching constraint on
`project.agents`; the sibling suite seeds with explicit
`created_at`/`updated_at`.

## Scope

One production caller (`register-command-center-routes.ts`), threaded in
this same change so the parameter has a supplier from the start rather
than becoming another inert seam of exactly the class this program keeps
finding.

The sync SQLite arm in the same file keeps its literals: that path
throws in backend mode and has no production caller, the same dead-arm
conclusion reached for `cleanupStaleMergeQueueRowsImpl` on #2839.

## Verification

`pnpm test:gate` green · both Command Center analytics suites 8/8 ·
`tsc` core 0, dashboard 0 · lint 0 · changeset included.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 16:09:14 -07:00
..
2026-07-26 18:11:47 -07:00