Files
fusion/docs/solutions/architecture-patterns
gsxdsm ed0df8b0e2 evidence: the self-healing sweeps do not RUN on a renamed board — 49 hardcoded column QUERIES, and 17/30 fakes hide it (#2800)
**Evidence only — no conversions, no behaviour change.** One doc, one
test. It changes how the fleet should read the largest remaining file in
the backlog.

## The finding, measured on `origin/main`

`packages/engine/src/self-healing.ts` carries:

- **97** lifecycle-column comparisons the census counts, and
- **49** calls of the shape `this.store.listTasks({ column: "<literal>",
… })`.

`listTasks`' option is `column?: ColumnId` — **one literal column**,
applied as a filter in the store. On a workflow whose lanes are renamed,
every one of those 49 queries returns an **empty array**, so the sweep
it feeds does nothing at all.

**The self-healing sweeps are not
mostly-correct-with-some-unconverted-guards. They never execute.** The
`in-review` family alone is roughly half the calls: merge recovery,
wedged merges, branch rebind, pending-step reconciliation.

## Why this matters to the census specifically

```ts
const tasks = await this.store.listTasks({ column: "done", slim: true });
const candidates = tasks.filter((task) =>
  task.column === "done" &&        // <-- the census counts THIS
  …
);
```

The census scores the **comparison**, not the query. Converting it is a
legal-looking change that drops a count and changes **nothing an
operator can observe** — the loop body still never runs, because the
list was already empty.

Roughly **31** of self-healing's remaining comparisons are this shape.
Driving `self-healing.ts` to 0 would report the subsystem as converted
while it stays inert on custom boards. In this file the census total is
not merely a floor — it is actively misleading, and I'd rather the fleet
know that before someone spends a week on the 97.

## Why the existing suite cannot see it

Measured across `packages/engine/src/__tests__/self-healing*.test.ts`:

- **30** files define a `listTasks` on their store fake.
- **17** ignore the `column` option entirely.

```ts
// representative of the 17
listTasks: vi.fn(async (options?: { limit?: number; offset?: number }) => {
  const all = [...tasksById.values()];   // options.column is never read
  return all.slice(offset, offset + limit);
}),
```

The fake is **more permissive than production**. The sweep receives rows
the real query would have filtered out, so the test proves the sweep's
*logic* while saying nothing about whether the sweep is ever *reached*.
A green self-healing suite is not evidence that self-healing runs.

This is the mirror image of
`store-fake-defects-that-masquerade-as-production-bugs.md`: there a fake
is *missing* something production needs and the code looks broken; here
it supplies *more* and the gap looks fixed.

## About the test

It **pins a known defect** and is labelled as such in the file header —
it asserts what the engine does today, which is the wrong thing.

It asserts the **query argument**, not the outcome. The outcome is `0`
either way, so an outcome assertion cannot distinguish *"nothing to do"*
from *"asked the wrong question"*. Asserting the argument also avoids
standing up the git-evidence path these sweeps enter once they have
candidates.

- **Ratchet proven to fire:** repointing `reconcileDoneTaskIntegrity`'s
query at the renamed lane makes it fail — `1 failed | 2 passed`. A guard
that reports success without checking anything is worse than no guard,
so I ran it.
- **Guard on the guard:** a first case asserts the renamed fixture
really does resolve a complete lane that is not `done`. Without it,
every later assertion could pass vacuously if the fixture ever collapsed
to the default vocabulary.
- **Control case:** shows the ignoring fake hands back a row whose
column is `shipped` from a query that asked for `done` — the mechanism
by which the suite stays green.

When the query layer is fixed this test will fail, forcing an update.
That is the intent.

## What I did NOT do, and why

I did not fix it. `column?: ColumnId` takes one id, and the resolution
is circular at the query layer — you need a task to know its workflow,
and you are querying to find the tasks. A real fix is either a
multi-column query option (`columns?: readonly ColumnId[]`) plus a
resolved union across live workflow definitions, or dropping the filter
and post-filtering by role in the engine.

Either is a **behaviour change to a shared store API across 49 call
sites**. That is a coordinator-level decision, not something a
conversion PR should take unilaterally — the same reasoning that kept
membership predicates out of the census. I'd take it on if you want it;
it needs to be a deliberate call, not a side effect of a conversion
sweep.

## Verification

- `pnpm test:gate` — 161 + 487 + 13 + 71, green
- `npx tsc -p packages/engine/tsconfig.json --noEmit` — clean
- `pnpm lint` — clean
2026-07-30 11:15:43 -07:00
..