Files
fusion/packages/engine
gsxdsm 8492278fdd U11 PR1: pin the merged intake+hold column contract before the IR moves (a mutation proved the first 7 tests insufficient) (#2495)
First of several small PRs for **U11** (merge Todo into Planning).
**Tests only — no production change.** It lands the precondition so the
IR edit arrives on proven substrate instead of an assumption.

## Decision taken (reversible, proceeding on it)

**The surviving Planning column keeps the id `todo`; `triage` is
deleted.** Same board the operator asked for — one column labelled
"Planning", no "Todo" — via the cheaper and safer half.

Measured, comments excluded, non-test, `packages/*/src` +
`dashboard/app`:

| | guards | writes | fallbacks | total |
|---|---:|---:|---:|---:|
| `"todo"` | 121 | 68 | 9 | 323 |
| `"triage"` | 90 | 30 | 21 | 304 |

Deleting `triage` instead of `todo` also means **no data migration**
(every live card in `todo` is already in the surviving column) and **no
guard changes meaning** (`column === "todo"` still denotes the hold
column). Under the plan's letter the opposite is true, and worse than
"dead": because Coding (Ideas) keeps `todo` per R10/R11, a surviving
`column === "todo"` guard would stay live for Ideas cards while silently
never matching for Coding cards — workflow-dependent, not dead.

This is also a proven in-tree pattern rather than a new idea:
**`builtin:coding-ideas` already ships this exact merge** — id `todo`,
display name "Planning", `hold(capacity)` + `reset-on-entry`,
plan-in-place.

Consequence worth flagging: **U11 no longer waits on Phase B.** The 121
`todo` guards keep their meaning, so converting them becomes U12 cleanup
rather than a U11 blocker.

## What this PR pins

Nothing in tree has ever carried `intake` and `hold` on one column.
Every built-in splits them. KTD-1 asserts the merged shape works; that
assertion was untested.

## The result, reported as found

**All 11 assertions passed on the first run against unmodified
sources.** The merged column is already supported by trait resolution,
the capacity sweep, and the release gate. **I could not make the first
seven fail**, so they are a regression floor — not evidence of a fix,
and I am not claiming them as one.

What makes them worth keeping is that they are *differential*: the same
scenario runs against the split-role vocabulary and the merged one and
asserts the role-level outcomes are **equal**, so a literal creeping
into any path fails the merged half while the split half stays green.

## The finding

**The first seven tests were not enough, and proving that is the point
of this PR.**

A mutation encoding the plausible-but-wrong belief *"an intake column
has no releaser"*:

```diff
- if (currentFlags.intake !== true && currentFlags.hold !== true) return false;
+ if (currentFlags.intake === true) return false;
+ if (currentFlags.hold !== true) return false;
```

left **all seven green**.

That belief is not hypothetical — it is stated verbatim in
`builtin-plan-review-group.ts`'s own FNXC comment as the reason Plan
Review lives in `todo` rather than `triage` today. Under U11 the
planning column **is** an intake column, so any code encoding it
silently stops holding unplanned cards and they release into
implementation with a bootstrap stub for a spec.

The gap: nothing reached `isUnplannedForExecution`. The mock store had
no `getTasksDir`, so both halves of the gate returned early — the tests
were exercising less than they appeared to. The fourth block drives it
with a real temp dir and a real bootstrap `PROMPT.md`.

**Re-running the same mutation now fails exactly one test — the
merged-column one — while its split-shape twin stays green.** That
discrimination is what the suite is for.

## Verification

26 tests green across this file plus `hold-release-renamed-columns`,
`hold-release-instrumentation`, and `pre-release-plan-review`. Lint
clean. No production file touched, so there is nothing to regress.

## Next PRs in this unit

1. Entry-contract test for `start` in a hold-carrying column (the
specific interaction the earlier, reverted attempt got wrong).
2. The ~10-line IR change itself — deliberately last, per KTD-7.
3. The intake-lane `triage` conversion: the 21 `?? "triage"` creation
defaults are the dangerous ones, since they would silently create cards
into a column that no longer exists.

`self-healing.ts` (11 triage guards + 4 writes) is the main worker's
file — I am not touching it and will hand over the line list rather than
race them.

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

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

## Summary by CodeRabbit

* **Tests**
* Added coverage for merged Planning column behavior across split and
merged workflow configurations.
* Verified intake and hold resolution, rebound targeting, capacity
hold/release outcomes, and execution gating.
* Confirmed planned cards are released appropriately while cards already
in progress are not unnecessarily held.

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

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 11:36:05 -07:00
..
2026-07-26 18:11:47 -07:00