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:
@@ -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;
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user