Files
fusion/docs/solutions/architecture-patterns
gsxdsm 9a11e0b136 U2b reproduction: the live move path accepts the column U11 deleted (characterized, not patched) (#2601)
Found while proving U11's caveat 2. **Characterization plus guard-rails
— no production change, deliberately.**

## The defect

A default-workflow card in Planning can be moved **into `triage`** — a
column its workflow no longer declares — re-creating exactly the
stranded state `reconcileUndeclaredTaskColumns` exists to repair.

Measured on a fresh store:

```
experimentalFeatures.workflowColumns   null            ← no production writer
createTask(...)                        column = "todo"
moveTask("todo" → "triage")            ACCEPTED
moveTask("todo" → "bogus-column")      REJECTED: "Valid targets: in-progress, triage, archived"
```

The second rejection is the tell. Validation is real — but it is the
**legacy `VALID_TRANSITIONS`** table talking, and that table does not
know the card's workflow. Its `todo` row still lists `triage`.

## Why the workflow-aware check does not run

`moves.ts` gates its adjacency block — including
`workflowHasColumn(workflowIr, toColumn)` — on
`isWorkflowColumnsCompatibilityFlagEnabled`, which reads the raw
`experimentalFeatures.workflowColumns` key. Nothing writes it, so the
block is dead on the path every real project takes.

**Corollary, already reported:** U11's undeclared-source escape hatch in
`resolveAllowedColumns` also does not run in production. It was added
with #2515 so a stranded card would have a legal move instead of `Valid
targets: none`; on the live path that rescue comes from the legacy table
instead. Mutation-verified — stubbing the hatch back to `[]` leaves the
operator-move test green.

## Why I did not fix it

PR #2499 un-gated the capacity check and **explicitly scoped validation
out**:

> SCOPE, deliberately narrow: only the CAPACITY check is un-gated.
`workflowIr` stays flag-gated so transition VALIDATION keeps its current
behavior — the inline path's bare-Error/"Valid targets:" contract is
unchanged, and none of the Phase A2 divergences are flipped here.

That is a considered decision by the owner of this function, and several
suites pin the contract it protects. Overriding it from outside would
flip an error shape I do not own.

**What has changed since that decision is U11:** the legacy table now
offers a target the default workflow does not declare, which it never
did before. That is new input to the scoping call, not licence to ignore
it — so this lands as a reproduction for U2b rather than a patch.

U2b's branch (`feature/workflow-move-path-convergence`) is stale — HEAD
predates several merged PRs, clean tree — so nothing is being raced.

## What ships

The defect is **characterized, not asserted-as-correct**: the test pins
today's behaviour so it is visible and measurable, and an `it.todo`
states the intended behaviour. Writing it as a passing "refuses" test
would have required the fix; writing it as a failing test would redden
CI; asserting the current behaviour as *correct* would be a lie.
Characterization plus `it.todo` is the honest third option.

Four guard-rails pin what a fix must **not** break:

- every declared lifecycle move (`todo → in-progress → in-review →
done`)
- archiving
- a `recoveryRehome` deliberately reaching an undeclared column — the
path that rescues already-stranded cards, and the one a careless fix
would break
- a premise test asserting the compatibility flag really is unset, so
the suite fails loudly if that ever changes rather than silently testing
a different code path

## Exposure

Narrow but real. U10 already fixed the dashboard move menu to offer only
workflow-declared targets, so the board does not present this. The
**write path** does — REST API, CLI, plugins, any stale client — which
is why the guard belongs in `moves.ts` rather than only in the UI.

5 passed + 1 todo; lint clean.

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Added coverage for task moves involving workflow-declared and
undeclared columns.
* Documented a known issue where tasks can currently be moved into the
deleted `triage` column.
  * Preserved valid moves, archiving, and recovery re-homing behavior.

* **Documentation**
* Added reproduction steps, affected move paths, and guardrails for
addressing the issue.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 21:12:57 -07:00
..