Files
fusion/packages
gsxdsm c643d62e85 fix(executor): wipDeclared must ask ALL six lifecycle roles, not two (#2777)
## What this fixes

`resolveResumeLanes` returns `wipDeclared`, which gates whether
`routeGraphFailureToExecutionResume` may route a graph failure back into
execution resume. Getting it wrong terminalizes tasks on boards that
should resume.

Two prior versions were wrong, both caught in review rather than by me
at write time:

**1. Two-state (`lifecycle?.wip !== undefined`)** — greptile P1 on
#2760. A v1-upgraded board terminalizes: `synthesizeDefaultColumns`
emits `{ id, name: id, traits: [] }`, so *every* role resolves
`undefined` even though those columns literally are the legacy lanes.
Verified by parsing a real v1 IR.

**2. Proxying "synthesized" as "hold and review are both undefined"** —
my own fix for (1), and also wrong. I caught this against #2765 rather
than shipping it. A **v2** board that declares only `intake` +
`complete` has hold and review undefined too, so it would be misread as
synthesized and treated as declaring wip when it deliberately does not.

The failure mode both versions share: reading a *sample* of the roles
and treating the answer as a verdict about the whole IR. #2765 says it
directly — an empty result has two meanings, and you cannot tell them
apart from a subset.

## The rule

```ts
wipDeclared: lifecycle?.wip !== undefined || !declaresAnyLifecycleRole(lifecycle),
```

Three states, asking all six roles:

- **wip declared** → true, the board says so.
- **some role declared but not wip** → false. A v2 board that omits wip
means it; do not resume into a lane it did not define.
- **no role declared at all** → true. That is the
synthesized/v1-upgraded shape, whose columns *are* the legacy lanes; the
pre-existing behaviour is correct there and must not regress.

`declaresAnyLifecycleRole` iterates `Object.values(lifecycle)` rather
than naming roles, so a seventh role added later is included
automatically instead of silently falling into the wrong branch.

## Evidence

- `executor-resume-lanes-resolved.test.ts`: **7 passed**, +23 lines
covering the v1-synthesized board and the declares-some-but-not-wip
board.
- **Mutation:** restoring the naive two-state rule → **1 failed / 6
passed**. The added coverage is load-bearing and pins exactly the
regression greptile caught.
- Gate **732 green** · `pnpm lint` clean · engine `tsc --noEmit` **0
errors**.
- Rebased on current main.

## Scope

`executor.ts` (+22) and its test (+23). One predicate; no other behavior
touched.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-30 09:28:41 -07:00
..
2026-07-26 18:11:47 -07:00