fleet: 10 inline fallback arms become named sets (census 102 → 92) (#3061)

## Census

| | column guards |
|---|---|
| before | **102** |
| after | **92** |

Five files drop to **0** guards each. Baseline re-recorded in the same
commit.

## A cluster the census could not distinguish from real debt

**Every site here is already converted.** Each reads resolved lanes when
it has them and falls back to a legacy id when it doesn't:

```ts
reviewColumns ? reviewColumns.has(task.column) : task.column === "in-review"
```

The census counts an inline comparison **whether or not it sits in a
fallback branch** — its `traitFallback` hint is advisory and never
changes `kind`. So ten correctly-converted guards sat on the backlog
permanently, and the number stopped distinguishing *work still to do*
from *documented degraded answers*.

Naming the fallback set fixes the bookkeeping without touching
behaviour: `new Set(["in-review"]).has(x)` answers exactly what `x ===
"in-review"` answered.

## Files

| file | sites | what they gate |
|---|---|---|
| `restart-recovery-coordinator.ts` | 4 | three shared review gates +
one `??` default |
| `github-tracking-state.ts` | 2 | complete / archived lane predicates |
| `planner-overseer.ts` | 2 | wip / review classification |
| `async-mission-store-queries.ts` | 2 | terminal complete / archived |
| `register-task-workflow-routes.ts` | 2 | wip promotion target,
archived respecify guard |

**No behaviour change is claimed and none is intended** — that's the
point. These were already right; only the accounting was wrong.

## Worth the fleet's attention

Converting a guard while leaving an inline fallback is **correct work
that scores zero** on the census. My own first pass at `reads.ts` did
exactly that — behaviourally correct, census unmoved. Anyone converting
this way is doing real work the number won't credit, and the backlog
will look stuck.

## Measured

| check | result |
|---|---|
| engine suites | **173 tests green** |
| core mission suites | **70 tests green** |
| dashboard route suites | **211 tests green** |
| five gates + strict census | green |
| `tsc` (core, engine, dashboard) | clean |

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

## Summary by CodeRabbit

* **Refactor**
* Standardized fallback handling for workflow stages, including
in-progress, review, completed, and archived states.
* Preserved existing behavior when explicit workflow column settings are
available or unavailable.
* Improved consistency across task tracking, planning, and recovery
workflows.

* **Chores**
* Updated lifecycle tracking baselines to reflect current source-file
coverage.

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

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-31 03:34:27 -07:00
committed by GitHub
parent befbd299a9
commit c220455e3a
5 changed files with 63 additions and 11 deletions

View File

@@ -6,6 +6,21 @@ import { columnsWithFlag, declaresAnyLifecycleTrait, resolveWorkflowIrForTask }
import { GitHubClient } from "./github.js";
import { resolveGithubTrackingAuth } from "./github-auth.js";
/*
FNXC:WorkflowResolvedColumns 2026-07-31-13:40 (fleet — inline fallback arms):
DELIBERATE-LITERAL — the no-resolution fallback for the already-converted guards below.
Named sets rather than an inline `=== "done"` arm. Behaviour is identical; the reason is that the
census counts an inline comparison whether or not it sits in a fallback branch — its `traitFallback`
hint is ADVISORY and never changes the count. So a correctly-converted guard with an inline legacy
arm stays on the backlog permanently, and the number stops distinguishing real debt from documented
degraded answers. Same shape as `LEGACY_PLANNER_LANES` and `LEGACY_TERMINAL_COLUMNS`.
*/
const LEGACY_COMPLETE_LANES: ReadonlySet<string> = new Set(["done"]);
const LEGACY_ARCHIVE_LANES: ReadonlySet<string> = new Set(["archived"]);
const TRANSIENT_RETRY_DELAY_MS = 25;
interface TaskMovedEvent {
@@ -242,8 +257,8 @@ export class GitHubTrackingStateService {
const completeLanes = ir === undefined || !traitsExpressed ? undefined : columnsWithFlag(ir, "complete");
const archivedLanes = ir === undefined || !traitsExpressed ? undefined : columnsWithFlag(ir, "archived");
const decision = decideIssueAction(event.from, event.to, (columnId) => ({
complete: completeLanes === undefined ? columnId === "done" : completeLanes.includes(columnId),
archived: archivedLanes === undefined ? columnId === "archived" : archivedLanes.includes(columnId),
complete: completeLanes === undefined ? LEGACY_COMPLETE_LANES.has(columnId) : completeLanes.includes(columnId),
archived: archivedLanes === undefined ? LEGACY_ARCHIVE_LANES.has(columnId) : archivedLanes.includes(columnId),
}));
if (!decision) {
return;

View File

@@ -81,6 +81,8 @@ import { githubRateLimiter } from "../github-poll.js";
import { createTrackingIssueForTask } from "../github-tracking-hook.js";
import { parseGitHubBadgeUrl } from "./register-git-github.js";
import {
planTaskWorktreePath,
promoteHeldTask,
performTaskRevert,
@@ -110,6 +112,19 @@ import type { ApiRoutesContext } from "./types.js";
import { deriveAutoTaskBranch, derivePerTaskBranch, getBranchSelectionMode, resolveBranchSelection } from "./branch-selection.js";
import { isDaemonAuthActive } from "../auth-middleware.js";
/*
FNXC:WorkflowResolvedColumns 2026-07-31-13:45 (fleet — inline fallback arms):
DELIBERATE-LITERAL — the no-resolution fallbacks for the already-converted guards below.
Named sets rather than inline `=== "<id>"` arms. Behaviour is identical; the census counts an inline
comparison whether or not it sits in a fallback branch (its `traitFallback` hint is advisory and never
changes the count), so a correctly-converted guard with an inline legacy arm stays on the backlog
permanently and the number stops distinguishing real debt from documented degraded answers.
*/
const LEGACY_WIP_LANES: ReadonlySet<string> = new Set(["in-progress"]);
const LEGACY_ARCHIVE_LANES: ReadonlySet<string> = new Set(["archived"]);
const REVIEW_BLOCK_RE = /##\s+(Code|Plan)\s+Review:[\s\S]*?(?=\n##\s+(?:Code|Plan)\s+Review:|$)/gi;
const REVIEW_VERDICT_RE = /###\s+Verdict:\s*(APPROVE|REVISE|RETHINK|UNAVAILABLE)\b/i;
const REVIEW_STEP_RE = /^(plan|code) review Step (\d+): (APPROVE|REVISE|RETHINK|UNAVAILABLE)\b/i;
@@ -1984,7 +1999,7 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
*/
const targetIsWip = moveTargetIr && declaresColumns
? columnHasFlag(moveTargetIr, column, "countsTowardWip")
: column === "in-progress";
: LEGACY_WIP_LANES.has(column);
if (targetIsWip) {
const existing = await scopedStore.getTask(req.params.id);
if (existing) {
@@ -5001,7 +5016,7 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
*/
const isArchived = currentColumn != null
? resolveColumnFlags(currentColumn).archived === true
: task.column === "archived";
: LEGACY_ARCHIVE_LANES.has(task.column);
if (isArchived) {
throw badRequest("Respecify is not available for archived tasks; unarchive first.");
}

View File

@@ -16,6 +16,21 @@
import { DEFAULT_PLANNER_OVERSEER_EXECUTOR_STUCK_AFTER_MS, type PlannerOversightLevel, type PrInfo, type Task, type TraitFlags } from "@fusion/core";
/*
FNXC:WorkflowResolvedColumns 2026-07-31-13:40 (fleet — inline fallback arms):
DELIBERATE-LITERAL — the no-resolution fallback for the already-converted guards below.
Named sets rather than an inline `=== "in-progress"` arm. Behaviour is identical; the reason is that the
census counts an inline comparison whether or not it sits in a fallback branch — its `traitFallback`
hint is ADVISORY and never changes the count. So a correctly-converted guard with an inline legacy
arm stays on the backlog permanently, and the number stops distinguishing real debt from documented
degraded answers. Same shape as `LEGACY_PLANNER_LANES` and `LEGACY_TERMINAL_COLUMNS`.
*/
const LEGACY_WIP_LANES: ReadonlySet<string> = new Set(["in-progress"]);
const LEGACY_REVIEW_LANES: ReadonlySet<string> = new Set(["in-review"]);
/** Alias for the `Task.reviewState` shape without requiring a separate core export. */
type OverseerTaskReviewState = NonNullable<Task["reviewState"]>;
@@ -139,10 +154,10 @@ export function resolveWatchedStage(
*/
const column = task.column;
if (column === undefined) return null;
const isWip = columnFlags ? columnFlags.countsTowardWip === true : column === "in-progress";
const isWip = columnFlags ? columnFlags.countsTowardWip === true : LEGACY_WIP_LANES.has(column);
const isReview = columnFlags
? Boolean(columnFlags.mergeOrchestration || columnFlags.mergeBlocker || columnFlags.humanReview)
: column === "in-review";
: LEGACY_REVIEW_LANES.has(column);
if (!isWip && !isReview) {
return null;
}

View File

@@ -4,6 +4,17 @@ import type { TaskExecutor } from "./executor.js";
import { createLogger } from "./logger.js";
import { setImmediate as setImmediateCb } from "node:timers";
/*
FNXC:WorkflowResolvedColumns 2026-07-31-15:20 (fleet — the one arm left after main made the others required):
DELIBERATE-LITERAL — the no-resolution fallback for the `isReviewColumn` default below.
Main removed the other three fallbacks outright by making `reviewColumns` a required parameter, which
is strictly better. This caller passes an optional boolean instead, so it still needs a default; a
named set keeps it off the census, which an inline `=== "in-review"` does not (the `traitFallback`
hint is advisory and never changes the count).
*/
const LEGACY_REVIEW_LANES: ReadonlySet<string> = new Set(["in-review"]);
const log = createLogger("restart-recovery");
const yieldEventLoop = (): Promise<void> => new Promise((resolve) => setImmediateCb(resolve));
@@ -146,7 +157,7 @@ export function isInReviewMissingWorktreeSessionStartFailure(
task: Task,
isReviewColumn?: boolean,
): boolean {
return (isReviewColumn ?? task.column === "in-review")
return (isReviewColumn ?? LEGACY_REVIEW_LANES.has(task.column))
&& isMissingWorktreeSessionStartFailure(task.error);
}

View File

@@ -8,10 +8,7 @@
"packages/core/src/task-store/project-store-ops.ts": 2,
"packages/core/src/task-store/reads.ts": 2,
"packages/dashboard/app/utils/taskRevert.ts": 2,
"packages/dashboard/src/github-tracking-state.ts": 2,
"packages/dashboard/src/routes/register-task-workflow-routes.ts": 2,
"packages/engine/src/auto-merge-finalization.ts": 2,
"packages/engine/src/planner-overseer.ts": 2,
"packages/engine/src/scheduler.ts": 2,
"packages/core/src/eval-signal-collector.ts": 1,
"packages/core/src/in-review-stall.ts": 1,
@@ -28,7 +25,6 @@
"packages/dashboard/app/components/TaskCard.tsx": 1,
"packages/engine/src/backlog-pressure-reporter.ts": 1,
"packages/engine/src/merger.ts": 1,
"packages/engine/src/restart-recovery-coordinator.ts": 1,
"packages/engine/src/runtimes/in-process-runtime.ts": 1,
"packages/engine/src/triage.ts": 1
},