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)
This commit is contained in:
@@ -121,6 +121,18 @@ an undeclared column — invisible to every trait-driven sweep until reconciliat
|
||||
throws. Whether that surfaces or disappears depends entirely on whether the caller catches, which is
|
||||
per-site and is **not** measured here — do not read "29" as "29 crashes".
|
||||
|
||||
## The one remaining non-blocked site, and why it is not converted
|
||||
|
||||
`packages/dashboard/app/utils/appLifecycle.ts:245` — the CLI-session **cancel** action, `deps.moveTask(session.id, "todo")`. Its dependency type pins the literal in the signature itself:
|
||||
|
||||
```ts
|
||||
moveTask: (id: string, column: "todo") => Promise<unknown>;
|
||||
```
|
||||
|
||||
Converting it needs the resolved hold lane threaded from the caller (`App.tsx#handleCliAction`), and there is **no resolved-columns map in that scope** — the app-side convention is an optional `columnFlagsById` passed down, as `deriveStatsFromTasks` does, and App does not have one here.
|
||||
|
||||
Adding the parameter without wiring it would be an **unwired parameter**, which is precisely the anti-pattern the caller audit (#2803) removed five of. So this is left for the dashboard-app owner, who can introduce the map at the same time. One site, recorded rather than half-done.
|
||||
|
||||
## What to do
|
||||
|
||||
1. **Convert the pair or neither.** When a census entry sits in a function that also performs a
|
||||
|
||||
Reference in New Issue
Block a user