Files
fusion/scripts/lib/lifecycle-column-census-baseline.json
gsxdsm ec2921b958 fleet(engine): self-healing 23 → 6 — async-reachable guards, plus 3 of 4 fan-out guards the sync path could not serve (#3094)
**Replaces #3093, which I am closing.** Third rebuild of this work.

## A coordination note first, because it is costing more than the code

`self-healing.ts` has had **three** overlapping conversions land from
other lanes while my branch was open — #3049, #3075, #3078. Every time,
replaying my commits produced conflicts that were all the same shape:
*same guard, two spellings, different variable names*. Each rebuild is a
full cycle spent on merge mechanics rather than on lanes.

I have rebuilt against `main`'s own census each time rather than argue
about whose spelling wins, and this PR contains only what `main` (23)
does not have. But if this file is going to keep receiving concurrent
fleet passes, one lane should own it — otherwise the next PR pays the
same tax again.

## Converted

| site | note |
|---|---|
| `isPhantomExecutorBinding` | caller resolved
`lanesOfReclaim(task.id).wip` **three lines above the call**, then
passed a task whose column the predicate compared against `in-progress`
|
| `isWorkspaceOwnerLive` | required `completeColumns` |
| `recoverPausedAbortFailures` **body** | #3075 converted this sweep's
*router* and left three body guards comparing ids |
| `reconcilePreExecutionWorktrees` | a four-id literal in a sweep that
**removes worktrees** |
| `recoverStarvedRefinementTriageTasks` | 2 peer counts that read zero,
so escalation never fired |
| `evaluateParkedAgentTaskLink` | the omitted `parkedColumns` argument |

All through the **async** `resolveProjectColumnsForRoles`, whose only
store read is `listWorkflowDefinitions()` — answerable under PostgreSQL.
That is what separates these from the inert kind.

**The half-converted sweep is the important one.** A router that
resolves correctly feeding a body that compares ids is worse than
converting neither: the route now fires on a renamed board and the body
then acts on the wrong lane. The `moveTask` **target** is the sharp end
— an undeclared target is rejected *except* under `recoveryRehome` with
a legacy id (`moves.ts:570`, the #1411 escape hatch), so a converted
route feeding the literal `"todo"` rehomes the card into a column its
workflow does not declare, which is the state other reconcilers exist to
repair.

**A real dropped-behaviour bug**: `evaluateParkedAgentTaskLink` was
called without `parkedColumns`, falling back to `LEGACY_PARKED_COLUMNS`.
A live durable agent linked to a card resting in a renamed hold lane
read as not-parked, so the safeguard preserving its task link never
applied.

## Withdrawn: the `task:moved` fan-out

I wrote the sync-IR conversion, measured it, removed it.
`getTaskWorkflowSelectionImpl` returns `undefined` **unconditionally**
under PostgreSQL, so `resolveTaskWorkflowIrSync` always answers with the
default builtin IR and `columnsWithFlag` on it yields exactly the legacy
ids — inert on every board.

Worse than the literal, because **the literal is counted**. My own test
passed only because its store mock supplied a renamed IR: it pinned the
helper's shape, not production behaviour. The refutation is recorded in
place, and `check-inert-sync-lane-conversions` exits 0 on this branch.

## One question, one answer

An earlier pass of this work mapped the notification-attach guard onto a
wider `activeWork` set, and `self-healing-paused-abort-recovery >
"rehomes an in-progress pause-abort park back to todo"` caught it — an
in-progress park attached a transition notification it should not have.

The fix is not a narrower set. Both guards ask **one** question — *"is
the card already at the requeue target?"* — which the literal happened
to spell twice as `=== "todo"`. The target now resolves once, before the
write, and both read it. Deriving one question two ways is exactly how a
converted guard and an unconverted target drift apart.

## Census

| | before | after |
|---|---|---|
| `self-healing.ts` | 23 | **11** |
| repo backlog | 53 | **41** |

## Measured

- `src/__tests__/self-healing*` + `task-agent*` — **42 files / 836 tests
pass**
- `tsc --noEmit -p packages/engine` clean;
**`check-inert-sync-lane-conversions` exits 0**; census `--strict`,
`check-lane-wiring`, `check-fnxc-future-dates` clean

## The remaining 11, flagged not guessed

- **4** — the fan-out, withdrawn above; blocked on a sync-capable
selection reader.
- **1** — the log-dedup closure: pre-existing flag; it sits before the
lane prefetch it needs, and the degraded answer costs a duplicate log
line, not a lifecycle decision.
- **1** — the synthetic `{ column: "todo" }` for a *missing* task:
deliberate, and now correct rather than unconverted, because
`parkedColumns` is legacy-seeded.
- The rest are status/deliberate classifications the census counts but
that are not lane guards.

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved workflow automation for boards using renamed or customized
workflow lanes.
* Fixed task completion fan-out, branch rebinding, recovery, and
stalled-task detection across custom lifecycle columns.
* Prevented completion actions from triggering when tasks move back to
the work-in-progress lane.
* Improved cleanup and pause recovery behavior for customized workflows.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 04:56:49 -07:00

136 lines
8.2 KiB
JSON

{
"generatedFrom": "node scripts/lifecycle-column-census.mjs --strict --update-baseline",
"byFile": {
"packages/engine/src/self-healing.ts": 6,
"packages/engine/src/executor.ts": 4,
"packages/dashboard/app/utils/taskRevert.ts": 2,
"packages/engine/src/auto-merge-finalization.ts": 2,
"packages/engine/src/scheduler.ts": 2,
"packages/core/src/eval-signal-collector.ts": 1,
"packages/core/src/mission-store.ts": 1,
"packages/core/src/task-store/async-comments-attachments.ts": 1,
"packages/core/src/task-store/audit-ops.ts": 1,
"packages/core/src/task-store/lifecycle-ops.ts": 1,
"packages/core/src/task-store/merge-queue-ops-2.ts": 1,
"packages/core/src/task-store/moves.ts": 1,
"packages/core/src/task-store/task-artifacts-ops.ts": 1,
"packages/core/src/task-store/task-id-integrity.ts": 1,
"packages/dashboard/app/components/ResearchTaskActionModal.tsx": 1,
"packages/dashboard/app/components/TaskCard.tsx": 1,
"packages/engine/src/notification/notification-service.ts": 1,
"packages/engine/src/triage.ts": 1
},
"deliberateByFile": {
"packages/core/src/task-store/async-comments-attachments.ts\u0000archived": 5,
"packages/dashboard/app/components/TaskContextMenu.tsx\u0000in-review": 3,
"packages/engine/src/self-healing.ts\u0000in-review": 3,
"packages/core/src/live-agent-count.ts\u0000in-progress": 2,
"packages/core/src/live-agent-count.ts\u0000in-review": 2,
"packages/core/src/store.ts\u0000in-review": 2,
"packages/core/src/task-merge.ts\u0000archived": 2,
"packages/core/src/task-merge.ts\u0000done": 2,
"packages/core/src/task-merge.ts\u0000in-review": 2,
"packages/core/src/task-store/project-store-ops.ts\u0000in-review": 2,
"packages/core/src/task-store/task-artifacts-ops.ts\u0000archived": 2,
"packages/dashboard/app/components/TaskCard.tsx\u0000todo": 2,
"packages/dashboard/app/components/TaskCard.tsx\u0000triage": 2,
"packages/dashboard/app/components/TaskContextMenu.tsx\u0000archived": 2,
"packages/dashboard/app/components/TaskContextMenu.tsx\u0000done": 2,
"packages/dashboard/app/components/TaskDetailModal.tsx\u0000triage": 2,
"packages/dashboard/app/hooks/useTaskDiffStats.ts\u0000done": 2,
"packages/dashboard/src/reliability-metrics.ts\u0000in-review": 2,
"packages/engine/src/cli-agent/state-machine.ts\u0000done": 2,
"packages/engine/src/scheduler.ts\u0000archived": 2,
"packages/engine/src/scheduler.ts\u0000done": 2,
"packages/engine/src/scheduler.ts\u0000in-progress": 2,
"packages/engine/src/scheduler.ts\u0000in-review": 2,
"packages/engine/src/self-healing.ts\u0000done": 2,
"packages/engine/src/usage-limit-detector.ts\u0000archived": 2,
"packages/engine/src/usage-limit-detector.ts\u0000done": 2,
"plugins/fusion-plugin-reports/src/store/report-store.ts\u0000archived": 2,
"packages/cli/src/commands/task.ts\u0000archived": 1,
"packages/cli/src/commands/task.ts\u0000done": 1,
"packages/cli/src/extension.ts\u0000archived": 1,
"packages/cli/src/extension.ts\u0000done": 1,
"packages/core/src/agent-store.ts\u0000archived": 1,
"packages/core/src/agent-store.ts\u0000done": 1,
"packages/core/src/async-mission-store-queries.ts\u0000archived": 1,
"packages/core/src/async-mission-store-queries.ts\u0000done": 1,
"packages/core/src/live-agent-count.ts\u0000archived": 1,
"packages/core/src/live-agent-count.ts\u0000done": 1,
"packages/core/src/plugin-store.ts\u0000done": 1,
"packages/core/src/store.ts\u0000archived": 1,
"packages/core/src/store.ts\u0000done": 1,
"packages/core/src/store.ts\u0000in-progress": 1,
"packages/core/src/store.ts\u0000todo": 1,
"packages/core/src/task-move-disposer.ts\u0000in-progress": 1,
"packages/core/src/task-move-disposer.ts\u0000todo": 1,
"packages/core/src/task-store/archive-lifecycle-2.ts\u0000archived": 1,
"packages/core/src/task-store/audit-ops.ts\u0000archived": 1,
"packages/core/src/task-store/branch-and-pr-entities.ts\u0000archived": 1,
"packages/core/src/task-store/comments-ops.ts\u0000archived": 1,
"packages/core/src/task-store/task-id-integrity.ts\u0000archived": 1,
"packages/core/src/task-store/task-store-helpers.ts\u0000in-progress": 1,
"packages/core/src/task-store/task-store-helpers.ts\u0000todo": 1,
"packages/core/src/task-store/task-update.ts\u0000in-progress": 1,
"packages/core/src/task-store/task-update.ts\u0000in-review": 1,
"packages/core/src/task-store/update-task-deps.ts\u0000archived": 1,
"packages/core/src/task-store/update-task-deps.ts\u0000done": 1,
"packages/dashboard/app/components/command-center/liveSnapshotMetrics.ts\u0000in-progress": 1,
"packages/dashboard/app/components/command-center/MissionControlPanel.tsx\u0000done": 1,
"packages/dashboard/app/components/command-center/MissionControlPanel.tsx\u0000in-review": 1,
"packages/dashboard/app/components/command-center/MissionControlPanel.tsx\u0000todo": 1,
"packages/dashboard/app/components/DocumentsView.tsx\u0000archived": 1,
"packages/dashboard/app/components/DocumentsView.tsx\u0000done": 1,
"packages/dashboard/app/components/RoutineEditor.tsx\u0000triage": 1,
"packages/dashboard/app/components/ScheduleForm.tsx\u0000triage": 1,
"packages/dashboard/app/components/ScheduleStepsEditor.tsx\u0000triage": 1,
"packages/dashboard/app/components/TaskCard.tsx\u0000done": 1,
"packages/dashboard/app/components/TaskContextMenu.tsx\u0000in-progress": 1,
"packages/dashboard/app/components/TaskContextMenu.tsx\u0000triage": 1,
"packages/dashboard/app/components/TaskDetailModal.tsx\u0000archived": 1,
"packages/dashboard/app/components/TaskDetailModal.tsx\u0000done": 1,
"packages/dashboard/app/components/TaskDetailModal.tsx\u0000in-progress": 1,
"packages/dashboard/app/components/TaskDetailModal.tsx\u0000in-review": 1,
"packages/dashboard/app/components/TaskDetailModal.tsx\u0000todo": 1,
"packages/dashboard/app/hooks/useBlockerFanout.ts\u0000archived": 1,
"packages/dashboard/app/hooks/useBlockerFanout.ts\u0000done": 1,
"packages/dashboard/app/hooks/useBlockerFanout.ts\u0000in-progress": 1,
"packages/dashboard/app/hooks/useBlockerFanout.ts\u0000in-review": 1,
"packages/dashboard/app/hooks/useBlockerFanout.ts\u0000todo": 1,
"packages/dashboard/app/utils/columnRoles.ts\u0000todo": 1,
"packages/dashboard/app/utils/quickAddStart.ts\u0000todo": 1,
"packages/dashboard/src/github-tracking-comments.ts\u0000done": 1,
"packages/dashboard/src/github-tracking-state.ts\u0000archived": 1,
"packages/dashboard/src/github-tracking-state.ts\u0000done": 1,
"packages/dashboard/src/gitlab-tracking-comments.ts\u0000in-progress": 1,
"packages/dashboard/src/reliability-metrics.ts\u0000in-progress": 1,
"packages/dashboard/src/routes/register-task-workflow-routes.ts\u0000todo": 1,
"packages/dashboard/src/routes/register-task-workflow-routes.ts\u0000triage": 1,
"packages/dashboard/src/server.ts\u0000archived": 1,
"packages/dashboard/src/task-planner-chat-context.ts\u0000done": 1,
"packages/dashboard/src/test/mockCoreEngine.ts\u0000in-review": 1,
"packages/engine/src/agent-heartbeat.ts\u0000archived": 1,
"packages/engine/src/agent-heartbeat.ts\u0000done": 1,
"packages/engine/src/cli-agent/task-session.ts\u0000done": 1,
"packages/engine/src/executor.ts\u0000in-progress": 1,
"packages/engine/src/hold-release.ts\u0000archived": 1,
"packages/engine/src/hold-release.ts\u0000done": 1,
"packages/engine/src/hold-release.ts\u0000in-review": 1,
"packages/engine/src/project-engine.ts\u0000in-review": 1,
"packages/engine/src/scheduler.ts\u0000todo": 1,
"packages/engine/src/self-healing.ts\u0000archived": 1,
"packages/engine/src/triage.ts\u0000triage": 1,
"plugins/fusion-plugin-even-realities-glasses/src/notifications/diff.ts\u0000done": 1,
"plugins/fusion-plugin-reports/src/store/report-types.ts\u0000archived": 1
},
"queryByFile": {
"packages/core/src/task-store/async-persistence.ts": 2,
"packages/core/src/task-store/archive-lifecycle-2.ts": 1,
"packages/core/src/task-store/async-archive-lineage.ts": 1,
"packages/core/src/task-store/async-self-healing.ts": 1,
"packages/engine/src/agent-tools.ts": 1,
"packages/engine/src/self-healing.ts": 1
}
}