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