## The defect
The enrich-mode task picker filtered with `task.column !== "archived"`.
On a board whose archive lane is renamed, that matched nothing — so
filed-away tasks stayed in the picker and an operator could attach
research findings to work they had deliberately archived.
## Census before / after
| | before | after |
|---|---|---|
| COLUMN guards (backlog) | 10 | **9** |
| `ResearchTaskActionModal.tsx` | 1 | **0 — converted** |
Baseline re-recorded in the same commit; `--strict` green.
## This site was declined twice, and I wrote the second wrong estimate
#3213 left it counted, correctly, on the note that was here — which was
mine. Both prior cost estimates were wrong, so this corrects my own
work:
1. **"Needs a data-fetch change"** — reasoned about
`columnFlagsByTaskId`, a per-**task** map built from board-resident
rows. Right that such a map can't help (archived rows are exactly what a
board map omits), but this guard asks a per-**column** question, so it
never needed one.
2. **"Needs prop threading, MainContent → ResearchView → here"** — right
that the answer is column-keyed, wrong about where it lives. `ListView`
builds `columnFlagsById` *inline*, which made it look like the owner.
The data is `useBoardWorkflows`, a hook already called from `App`,
`Board`, and `HeaderWorkflowSwitcherSlot`.
**Measured cost: one file.** The modal already takes `projectId`, and
`ResearchView` renders it only when a finding is open (`open` hardcoded
beside `if (!finding) return null`) — so the hook cannot fetch for a
closed modal, which was the one real objection to calling it here.
Union across workflows keyed by column id, first declaration wins — the
same convention `ListView` uses, so the two cannot disagree about a
shared id. `isArchivedColumnRole` fail-softs to the legacy id when a
column has no flags, so an unresolved workflow behaves exactly as the
literal did.
## Tests — the invariant, not the repro
Per the surface-enumeration rule, four cases: renamed archive lane,
legacy id, unresolved workflow (fail-soft), and a second workflow's
archive lane through the cross-workflow union. A repro-only test would
pass on the legacy board and prove nothing about the case the guard
exists for.
**Anti-vacuity control:**
| | renamed lane | union | legacy id | fail-soft |
|---|---|---|---|---|
| pre-fix literal | **FAIL** | **FAIL** | pass | pass |
| converted | pass | pass | pass | pass |
The legacy and fail-soft cases hold in both directions **on purpose** —
they pin that this conversion did not change the pre-resolution answer.
Flagging that so 4/4 isn't read as four independent proofs.
## Measured
| check | result |
|---|---|
| `census --strict` / `check-fnxc-future-dates` | exit 0 / exit 0 |
| `eslint` | clean |
| `tsc -p tsconfig.app.json` (the config that actually covers `app/`) |
exit 0 |
| new tests | 4/4 |
| `pnpm test:gate` | exit 0 (744 tests) |
## Note on process
My first attempt at the control silently did nothing — the revert script
threw a `SyntaxError`, so the "pre-fix" run was the fixed code and
reported 4/4. Caught it because the error printed. The table above is
from the re-run.