From c220455e3ae8d130d7150d82dddc6a618377dbe4 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 31 Jul 2026 03:34:27 -0700 Subject: [PATCH] =?UTF-8?q?fleet:=2010=20inline=20fallback=20arms=20become?= =?UTF-8?q?=20named=20sets=20(census=20102=20=E2=86=92=2092)=20(#3061)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 | ## 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. Co-authored-by: Claude Opus 5 (1M context) --- .../dashboard/src/github-tracking-state.ts | 19 +++++++++++++++++-- .../routes/register-task-workflow-routes.ts | 19 +++++++++++++++++-- packages/engine/src/planner-overseer.ts | 19 +++++++++++++++++-- .../src/restart-recovery-coordinator.ts | 13 ++++++++++++- .../lib/lifecycle-column-census-baseline.json | 4 ---- 5 files changed, 63 insertions(+), 11 deletions(-) diff --git a/packages/dashboard/src/github-tracking-state.ts b/packages/dashboard/src/github-tracking-state.ts index 70ba9aa532..572408716b 100644 --- a/packages/dashboard/src/github-tracking-state.ts +++ b/packages/dashboard/src/github-tracking-state.ts @@ -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 = new Set(["done"]); +const LEGACY_ARCHIVE_LANES: ReadonlySet = 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; diff --git a/packages/dashboard/src/routes/register-task-workflow-routes.ts b/packages/dashboard/src/routes/register-task-workflow-routes.ts index 7a77e3b8eb..cc5540dde1 100644 --- a/packages/dashboard/src/routes/register-task-workflow-routes.ts +++ b/packages/dashboard/src/routes/register-task-workflow-routes.ts @@ -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 `=== ""` 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 = new Set(["in-progress"]); +const LEGACY_ARCHIVE_LANES: ReadonlySet = 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."); } diff --git a/packages/engine/src/planner-overseer.ts b/packages/engine/src/planner-overseer.ts index 951732f90b..d506f4ac3c 100644 --- a/packages/engine/src/planner-overseer.ts +++ b/packages/engine/src/planner-overseer.ts @@ -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 = new Set(["in-progress"]); +const LEGACY_REVIEW_LANES: ReadonlySet = new Set(["in-review"]); + + + /** Alias for the `Task.reviewState` shape without requiring a separate core export. */ type OverseerTaskReviewState = NonNullable; @@ -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; } diff --git a/packages/engine/src/restart-recovery-coordinator.ts b/packages/engine/src/restart-recovery-coordinator.ts index d80d211529..1813b046f0 100644 --- a/packages/engine/src/restart-recovery-coordinator.ts +++ b/packages/engine/src/restart-recovery-coordinator.ts @@ -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 = new Set(["in-review"]); + const log = createLogger("restart-recovery"); const yieldEventLoop = (): Promise => 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); } diff --git a/scripts/lib/lifecycle-column-census-baseline.json b/scripts/lib/lifecycle-column-census-baseline.json index e1acab8a7a..d711aba5b8 100644 --- a/scripts/lib/lifecycle-column-census-baseline.json +++ b/scripts/lib/lifecycle-column-census-baseline.json @@ -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 },