Files
fusion/docs/solutions
gsxdsm ed6d54485b glasses plugin: the review actions could never resolve a review lane (4 guards + 3 invisible destinations) (#2816)
Four agent actions still keyed on literals, with three census-invisible
`moveTask` destinations between them. `agent-actions.ts` already had
`laneContext`/`destination` from an earlier partial conversion — these
were simply never migrated.

## Census

| file | main | here |
| --- | ---: | ---: |
| `plugins/fusion-plugin-even-realities-glasses/src/agent-actions.ts` |
4 | **0** |

Plus 3 hardcoded `moveTask` destinations the census cannot see
(`requestReview` → `in-review`, `returnToAgent` → `todo`, `retryTask` →
`todo`).

## The real finding: this plugin could never resolve a review lane

`resolveLifecycleColumns` keys its `review` role on the
**`mergeOrchestration` trait alone**. A board whose review column
carries only `merge-blocker` and/or `human-review` — the common custom
shape, since `merge` is opt-in — resolves **no review lane at all**.

So every review-gated action here (`requestReview`, `acceptReview`,
`returnToAgent`, `retryTask`) compared against `undefined` and **refused
every card**, and `requestReview` had nowhere to move one. This is not a
regression from converting them; it is why they *could not* be converted
with `lanes.review` as-is.

Converting the four guards without noticing would have shipped four
actions that fail closed on exactly the boards this program exists to
support — a conversion that looks complete, passes its suite, and makes
the plugin useless on a custom board.

**Widened in `laneContext`, not in the shared resolver.**
`resolveLifecycleColumns` is consumed well beyond this plugin, and its
`review` role deliberately means "the merge-orchestration column" for
the merge queue. The gap is already recorded in
`notification-renamed-lifecycle-columns.test.ts` and in #2807 —
reconciling the two definitions is a core-level decision, not one to
take from a plugin. `mergeBlocker` is preferred over `humanReview`
because a card cannot leave a merge-blocking column until the gate
clears, which is the closer analogue of the legacy `in-review`.

## The suite caught an over-reach of mine

My first version put a blanket `if (degraded) conflict(...)` at the top
of `retryTask`, which broke a pinned invariant the test names outright:
**"a degraded workflow does not block retries that move nothing."** The
status-only retry just clears fields; refusing it because the workflow
could not be read breaks a recovery that needs no lane at all.

Degraded now blocks only the branches that actually **move**. Same
reasoning applied to `acceptReview`, which also moves nothing. The
existing `startWork` convention — conflict on degraded — is right
precisely *because* it moves.

## Ordering

`returnToAgent` and `retryTask` now resolve their destination **before**
the field clear. Both cleared first, so a rejected move left the
assignee and status — or the worktree, branch and base refs — nulled
with the card exactly where it was. That is the fifth instance of this
half-applied shape in the audit, and it is rule 3 in the class doc.

## Revert results (measured, each independently)

| conversion | reverted → |
| --- | --- |
| `requestReview` destination | 1 failed — moves to the literal
`in-review`, which this workflow does not declare |
| `returnToAgent` destination | 1 failed — moves to the literal `todo`,
same |

Plus a non-vacuous companion: a renamed card *not* in the wip lane must
still be refused by `requestReview`, so a gate admitting everything
would not pass.

## Verification

- Plugin suite — **186/186**
- `pnpm test:gate` — 161 + 487 + 13 + 71, green
- `tsc` on the plugin — clean
- `pnpm lint`, `check:changesets`, census `--strict` — all clean (run
explicitly)
2026-07-30 12:55:38 -07:00
..