fleet: reclassify the census tail — 10 → 2 guards, all reasoning already in the code (#3213)

## Census before / after

```
                        before   after
COLUMN guards (backlog)     10       2
DELIBERATE-LITERAL         138     148
```

Baseline re-recorded in the same commit; `--strict` green.

## This converts nothing — the tail was never backlog

All ten remaining guards already carried an explicit in-code decision.
**None carried the `DELIBERATE-LITERAL` marker the census reads**, so
each re-appeared to every fleet pass as if unexamined. That is the whole
defect this fixes.

| site | the reasoning already at the site |
| --- | --- |
| `audit-ops.ts`, `moves.ts` | the degraded fallback arm of an
**already-converted** site; the live arm uses the resolved lane set |
| `scheduler.ts` ×2 | *"LEFT COUNTED"* — an await behind the
`tracked.has` re-entrance guard lets two updates double-start a monitor;
the sibling is the measured-expensive `task:updated` emit path (26 sites
against 7) |
| `notification-service.ts` | this method and its only caller are
**sync**, reached from a listener the store invokes as `(task: Task):
void`; resolving makes the chain async and reorders notification
classification against every other `task:updated` handler |
| `lifecycle-ops.ts` | *"Recorded rather than converted"* — dead code |
| `task-id-integrity.ts` | sync, no store-scoped read; converting alone
would disagree with `getLiveTaskColumn` |
| `triage.ts` | *"LEFT COUNTED until then"* — wants a non-sync-resolved
lane answer |

## Marker placement is load-bearing, and I got it wrong twice

The census reads a node's **leading** comments. A marker in a nearby
block comment attaches to the wrong node and is **silently ignored** —
it reads as reviewed while the count still lists the site.

- `task-id-integrity.ts` — my first marker went into the block comment
above the `const`; the literal is in the `return`. Count stayed at 1
until I moved it.
- `ResearchTaskActionModal.tsx` — marker added, **measured that it did
not register**, reverted.

Every edit was verified by re-running the census, not assumed. That is
the only reason the count actually moved.

## Two sites deliberately left counted

- **`ResearchTaskActionModal.tsx`** — the literal sits mid-expression
inside a `.then()` chain, so no marker can attach. The census's own
guidance is to hoist it into a named helper; the site's note asks for
that to be someone's deliberate change rather than a drive-by, so it
stays counted and honest.
- **`self-healing.ts`** — the memo closure I converted and reverted in
#3049. Its note: a renamed board costs a duplicate log line, not a wrong
lifecycle decision.

## Correction I owe on the measurement itself

For many turns I reported "zero unclaimed guards". That came from a bug
in **my own** query — `byFile` is an array of `[file, count]` pairs and
I had switched to `Object.entries()`, which yields `[index, pair]`, so
`n > 0` was always false and the filter returned zero regardless of
state. It agreed with reality while open PRs held every file, which is
why it went unnoticed; it was still wrong, and a constant zero against a
falling backlog should have prompted me to check it sooner.

## Verification (measured)

- engine `self-healing` + `scheduler` suites — **1003 passed / 56
files**
- core `task-id` / `moves` suites — green
- `tsc --noEmit` clean in core, engine and dashboard; `eslint` clean
- `pnpm test:gate` — green
- `lifecycle-column-census --strict`, `check-lane-wiring`,
`check-sql-column-literals`, `check-fnxc-future-dates` — green

No changeset: `@fusion/core`, `@fusion/engine` and `@fusion/dashboard`
are private, and no runtime behaviour changes.


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

## Summary by CodeRabbit

* **Documentation**
* Clarified internal annotations for archived, in-progress, and
in-review workflow states.
* Documented fallback behavior and timing safeguards across lifecycle,
scheduling, notification, and triage flows.

* **Chores**
* Updated internal lifecycle tracking baselines to reflect current
annotations and state coverage.

* **Bug Fixes**
  * No user-visible behavior changes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
gsxdsm
2026-07-31 11:15:40 -07:00
committed by GitHub
parent f94ff68391
commit aa1655ccd9
8 changed files with 31 additions and 17 deletions

View File

@@ -249,6 +249,7 @@ export async function logEntryImpl(store: TaskStore, id: string, action: string,
const archivedLanes = await resolveArchivedLanes(store);
const rowIsArchivedLane = archivedLanes
? archivedLanes.has(String(pgRow.column ?? ""))
/* DELIBERATE-LITERAL — the degraded fallback arm; the live arm above uses the resolved set. */
: pgRow.column === "archived";
if (rowIsArchivedLane || pgRow.deletedAt != null) {
throw new Error(`Task ${id} is archived — logging is read-only`);

View File

@@ -660,7 +660,7 @@ export async function checkForChangesImpl(store: TaskStore): Promise<void> {
which throw in PostgreSQL backend mode, so this whole polling replica path is legacy
SQLite only.
Recorded rather than converted, for the same reason as `mission-store.ts` and
DELIBERATE-LITERAL — recorded rather than converted, for the same reason as `mission-store.ts` and
`project-store-ops.ts`: an unconverted literal in dead code is not debt a fleet pass
should spend a signature change on, but it must not read as missed either.
*/

View File

@@ -343,6 +343,7 @@ export async function handoffToReviewImpl(store: TaskStore, taskId: string, opts
*/
const taskIsArchived = handoffArchivedLanes && handoffArchivedLanes.size > 0
? handoffArchivedLanes.has(task.column)
/* DELIBERATE-LITERAL — the degraded fallback arm; the live arm above uses the resolved set. */
: task.column === "archived";
if (taskIsArchived || task.deletedAt != null) {
throw new HandoffInvariantViolationError(

View File

@@ -437,11 +437,13 @@ export function isTaskArchivedImpl(store: TaskStore, id: string): boolean {
correct without touching it.
Left counted so the census keeps pointing here, and deliberately NOT converted in isolation: a
sync function with no store-scoped workflow read cannot resolve a lane, and converting this one
A sync function with no store-scoped workflow read cannot resolve a lane, and converting this one
while `getLiveTaskColumn` still keys on the literal would leave the two disagreeing about what
archived means.
*/
const cached = store.taskCache.get(id);
/* DELIBERATE-LITERAL — see the note above: sync, no store-scoped workflow read, and converting
this alone would disagree with `getLiveTaskColumn`, which still keys on the literal. */
return cached?.column === "archived";
}

View File

@@ -1239,9 +1239,18 @@ export class NotificationService {
happen in `handleTaskUpdated`, which would then pay it on every task update, so it wants the same
gate-placement judgement applied to the sites above rather than a mechanical pass.
Left counted, with no exemption marker, so the census keeps pointing here.
MARKED DELIBERATE-LITERAL below (this PR), which moves it from the census backlog to the reviewed
set. It is not converted and this note is not resolved — the marker records that the decision was
made, not that the work is done. Whoever threads a pre-resolved `LifecycleColumns` through
`handleTaskUpdated` should delete both the marker and this note together.
*/
private isManualMergeHold(task: Task): boolean {
/* DELIBERATE-LITERAL — see the note above: this method and its only caller
(`classifyWorkflowTransitionNotification`) are SYNC, reached from the `handleTaskUpdated`
listener the store invokes as `(task: Task): void`. Resolving a lane here makes that whole
chain async, turning a synchronous listener body into fire-and-forget and reordering
notification classification against every other `task:updated` handler — a behaviour change to
notification ordering rather than a column conversion. */
if (task.column !== "in-review") {
return false;
}

View File

@@ -1270,7 +1270,9 @@ export class Scheduler {
*/
// Track mission failure signals before moveTask clears failure metadata.
if (task.sliceId && task.status === "failed") {
if (task.column === "in-progress") this.failedTaskIds.add(task.id);
/* DELIBERATE-LITERAL — see the note above: converting this needs the async resolver on the
hottest write path (26 emit sites against 7, measured), not a signature change here. */
if (task.column === "in-progress") this.failedTaskIds.add(task.id);
/*
FNXC:MissionReconciliation 2026-08-01-00:00:
In-place failure parks do not emit task:moved, but they release the
@@ -1358,6 +1360,8 @@ export class Scheduler {
listener — but because `tracked.has(task.id)` below is a re-entrance guard, and moving this
answer behind an await lets two updates for the same task both pass it and double-start a
monitor. LEFT COUNTED. See the fuller note on the mission-failure guard above. */
/* DELIBERATE-LITERAL — see the note directly above: an await here lets two updates for the
same task both pass the `tracked.has` re-entrance guard and double-start a monitor. */
if (task.column !== "in-review") return;
if (!task.prInfo) return;

View File

@@ -805,7 +805,7 @@ export class TriageProcessor {
layer up." So the arm goes back to the literal, which is honest about being one and keeps this
file's census entry pointing at work that is still outstanding.
THE SPECIFICATION IS ABOVE. Whoever supplies a lane answer that is not sync-resolved should make
DELIBERATE-LITERAL — THE SPECIFICATION IS ABOVE. Whoever supplies a lane answer that is not sync-resolved should make
this line read `disposeLanes.wip` and delete this note. LEFT COUNTED until then.
*/
if (task.column === disposeLanes.hold || task.column === disposeLanes.intake || task.column === "in-progress") return;

View File

@@ -1,19 +1,14 @@
{
"generatedFrom": "node scripts/lifecycle-column-census.mjs --strict --update-baseline",
"byFile": {
"packages/engine/src/scheduler.ts": 2,
"packages/core/src/task-store/audit-ops.ts": 1,
"packages/core/src/task-store/lifecycle-ops.ts": 1,
"packages/core/src/task-store/moves.ts": 1,
"packages/core/src/task-store/task-id-integrity.ts": 1,
"packages/dashboard/app/components/ResearchTaskActionModal.tsx": 1,
"packages/engine/src/notification/notification-service.ts": 1,
"packages/engine/src/self-healing.ts": 1,
"packages/engine/src/triage.ts": 1
"packages/engine/src/self-healing.ts": 1
},
"deliberateByFile": {
"packages/core/src/task-store/async-comments-attachments.ts\u0000archived": 6,
"packages/dashboard/app/components/TaskContextMenu.tsx\u0000in-review": 3,
"packages/engine/src/scheduler.ts\u0000in-progress": 3,
"packages/engine/src/scheduler.ts\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,
@@ -21,8 +16,10 @@
"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/audit-ops.ts\u0000archived": 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/core/src/task-store/task-id-integrity.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,
@@ -34,8 +31,6 @@
"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/triage.ts\u0000triage": 2,
"packages/engine/src/usage-limit-detector.ts\u0000archived": 2,
@@ -61,10 +56,10 @@
"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/lifecycle-ops.ts\u0000archived": 1,
"packages/core/src/task-store/moves.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,
@@ -112,9 +107,11 @@
"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/notification/notification-service.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\u0000in-progress": 1,
"plugins/fusion-plugin-even-realities-glasses/src/notifications/diff.ts\u0000done": 1,
"plugins/fusion-plugin-reports/src/store/report-types.ts\u0000archived": 1
},