fleet: 4 long-tail fallback arms become named sets (census 101 → 97) (#3064)

## Census

| | column guards |
|---|---|
| before | **101** |
| after | **97** |

The single-guard long tail is **19 files**. This converts the four whose
legacy arm is unambiguously a fallback on an already-converted guard;
the other 15 are flagged below rather than guessed at.

## Two shapes

**`in-review-stall.ts`, `stalled-review-detector.ts`** — the resolved
answer with an inline legacy arm:

```ts
reviewColumns ? reviewColumns.has(col) : col === "in-review"
→ (reviewColumns ?? LEGACY_REVIEW_LANES).has(col)
```

**`merger.ts`, `in-process-runtime.ts`** — belt-and-braces:

```ts
col !== (lifecycle?.complete ?? "done") && col !== "done"
```

That accepted the resolved lane **or** the legacy id, stated twice. A
union set says it once, so the two halves can't drift apart — which is
the real risk with a duplicated condition.

## A finding for anyone else marking fallbacks

`in-review-stall.ts` **already carried a `DELIBERATE-LITERAL` marker**
on that arm and was counted anyway. The marker sits in a comment *inside
a ternary*, which the census's leading-comment lookup doesn't reach.

So: **naming the set works, marking it does not.** Worth knowing before
someone marks a fallback and expects the count to move.

## No behaviour change

`new Set(["in-review"]).has(x)` answers exactly what `x === "in-review"`
answered, and the union sets accept exactly the two lanes their
conditions already accepted.

## Flagged, not converted

The remaining 15 single-guard sites need individual judgement, not a
mechanical pass:

- **plain unconverted guards with no resolution in scope** —
`audit-ops`, `lifecycle-ops`, `merge-queue-ops`, `task-id-integrity`,
`backlog-pressure-reporter`, `ephemeral-worker-manager`,
`ResearchTaskActionModal`
- **sites where the literal IS the answer** — `eval-signal-collector`
maps a column to an archive-vs-done *label*; `TaskCard` reads a
completion timestamp
- **already resolved on their line** — `triage.ts`,
`restart-recovery-coordinator.ts`, both covered by open PRs

## Measured

| check | result |
|---|---|
| core stall suites | 4 files, **85 tests green** |
| engine merger/runtime suites | **1044 tests green** |
| five gates + strict census | green |
| `tsc` (core, engine) | clean |

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-31 03:37:18 -07:00
committed by GitHub
parent c220455e3a
commit 8eef8852a0
5 changed files with 57 additions and 13 deletions

View File

@@ -1,6 +1,18 @@
import { getTaskMergeBlocker } from "./task-merge.js";
import type { Task, TaskLogEntry } from "./types.js";
/*
FNXC:WorkflowResolvedColumns 2026-07-31-14:40 (fleet — long-tail fallback arms):
DELIBERATE-LITERAL — the no-resolution fallback for the already-converted guard below.
A named set rather than an inline `=== "<id>"` arm. 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 `kind`), 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_REVIEW_LANES: ReadonlySet<string> = new Set(["in-review"]);
/**
* State-based in-review stall detection. This is complementary to FN-4168's
* planned heuristic `stalledReview` signal.
@@ -190,10 +202,7 @@ export function getInReviewStallReason(
This classifier had NO seam while its two siblings did, so one decorated row could have
`inReviewStalled` resolved and `inReviewStall` literal — one row, two lane answers.
*/
const inReviewLane = context.reviewColumns
? context.reviewColumns.has(task.column)
/* DELIBERATE-LITERAL — the no-metadata fallback. */
: task.column === "in-review";
const inReviewLane = (context.reviewColumns ?? LEGACY_REVIEW_LANES).has(task.column);
if (!inReviewLane || task.paused === true) {
return undefined;
}

View File

@@ -1,5 +1,17 @@
import type { Task } from "./types.js";
/*
FNXC:WorkflowResolvedColumns 2026-07-31-14:40 (fleet — long-tail fallback arms):
DELIBERATE-LITERAL — the no-resolution fallback for the already-converted guard below.
A named set rather than an inline `=== "<id>"` arm. 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 `kind`), 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_REVIEW_LANES: ReadonlySet<string> = new Set(["in-review"]);
/**
* Heuristic-only stalled review detector.
*
@@ -60,9 +72,7 @@ export function detectStalledReview(
task: Pick<Task, "column" | "paused" | "log">,
options?: { now?: number; windowMs?: number; reviewColumns?: ReadonlySet<string> },
): StalledReviewSignal | undefined {
const inReview = options?.reviewColumns
? options.reviewColumns.has(task.column)
: task.column === "in-review";
const inReview = (options?.reviewColumns ?? LEGACY_REVIEW_LANES).has(task.column);
if (!inReview || task.paused === true || task.log.length === 0) {
return undefined;
}

View File

@@ -118,6 +118,18 @@ import { describeModel, promptWithFallback } from "./pi.js";
import { accumulateSessionTokenUsage } from "./session-token-usage.js";
import { createResolvedAgentSession, extractRuntimeHint, resolveMergerSessionModel, resolveMergerThinkingLevel, resolveMergerFallbackThinkingLevel } from "./agent-session-helpers.js";
import { createFallbackModelObserver } from "./fallback-model-observer.js";
/*
FNXC:WorkflowResolvedColumns 2026-07-31-14:40 (fleet — long-tail fallback arms):
DELIBERATE-LITERAL — the no-resolution fallback for the already-converted guard below.
A named set rather than an inline `=== "<id>"` arm. 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 `kind`), 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_COMPLETE_LANES: readonly string[] = ["done"];
import { buildSessionSkillContext } from "./session-skill-context.js";
import { resolveMcpServersForStore } from "./mcp-resolution.js";
import { classifyTaskWorktree, getRegisteredWorktreeBranches, isRepoRootPath, RemovalReason, removeWorktree, type WorktreePool } from "./worktree-pool.js";
@@ -4943,7 +4955,10 @@ export async function findWorktreeUser(
if (t.id === excludeTaskId) continue;
if (t.worktree !== worktreePath) continue;
const lifecycle = await resolveTaskLifecycleColumns(store, t.id, conflictIrCache);
if (t.column !== (lifecycle?.complete ?? "done") && t.column !== "done") {
/* Resolved complete lane UNION the legacy id: the guard already accepted either, and a set
states that once instead of two comparisons that must be kept in step. */
const completeLanes = new Set<string>([lifecycle?.complete, ...LEGACY_COMPLETE_LANES].filter((c): c is string => c !== undefined));
if (!completeLanes.has(t.column)) {
return t.id;
}
}

View File

@@ -67,6 +67,18 @@ import { createRunAuditor, generateSyntheticRunId } from "../run-audit.js";
import { setImmediate as setImmediateCb } from "node:timers";
import { seedPreReleasePlanReviewContinuation } from "../plan-review-continuation.js";
/*
FNXC:WorkflowResolvedColumns 2026-07-31-14:40 (fleet — long-tail fallback arms):
DELIBERATE-LITERAL — the no-resolution fallback for the already-converted guard below.
A named set rather than an inline `=== "<id>"` arm. 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 `kind`), 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_ARCHIVE_LANES: readonly string[] = ["archived"];
const yieldEventLoop = (): Promise<void> => new Promise((resolve) => setImmediateCb(resolve));
/**
@@ -2473,7 +2485,9 @@ export class InProcessRuntime
const archivedLifecycle = await resolveTaskLifecycleColumns(this.taskStore, data.task.id)
.catch(() => undefined);
const archivedColumn = archivedLifecycle?.archived ?? "archived";
if (data.to !== archivedColumn && data.to !== "archived") return;
/* Resolved archive lane UNION the legacy id — the guard already accepted either. */
const archivedLanes = new Set<string>([archivedColumn, ...LEGACY_ARCHIVE_LANES]);
if (!archivedLanes.has(data.to)) return;
await this.chatStore?.deleteSessionsForAgentId(
`${TASK_PLANNER_CHAT_AGENT_ID_PREFIX}${data.task.id}`,
{ projectId: this.config.projectId },

View File

@@ -11,9 +11,7 @@
"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/in-review-stall.ts": 1,
"packages/core/src/mission-store.ts": 1,
"packages/core/src/stalled-review-detector.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,
@@ -24,8 +22,6 @@
"packages/dashboard/app/components/ResearchTaskActionModal.tsx": 1,
"packages/dashboard/app/components/TaskCard.tsx": 1,
"packages/engine/src/backlog-pressure-reporter.ts": 1,
"packages/engine/src/merger.ts": 1,
"packages/engine/src/runtimes/in-process-runtime.ts": 1,
"packages/engine/src/triage.ts": 1
},
"deliberateByFile": {