docs(lanes): audit the last three files the census points at with no reason attached (#2908)

Second pass of #2873's sweep, over the files that still carry lifecycle
guards and **zero** audit notes. No source change — every literal stays
counted, none gets an exemption marker.

## `project-store-ops.ts` (1) — dead sync path, do **not** convert

The literal would leak a merge-queue entry on a renamed board: a card
leaving review would never be dequeued. Except the function cannot run —
it reaches for `store.db.prepare`, which throws in PostgreSQL backend
mode.

The live path is `dequeueMergeQueueOnColumnExitInTransaction`
(`async-merge-coordination.ts`, called from `moves.ts`), and it is
**already converted** — it takes `moveReviewColumns` and the caller
supplies them. Recorded so the census entry is not mistaken for
unconverted debt, and so it can be deleted alongside the rest of the
sync SQLite residue.

## `task-id-integrity.ts` (2) — one real, one sentinel, and the real one
must not go alone

```ts
return cached?.column === "archived";   // ← board lane: real
if (live === "archived") return true;   // ← getLiveTaskColumn's manufactured value: sentinel
```

Converting the first while `getLiveTaskColumn` still keys on the literal
would leave the two disagreeing about what "archived" means. It waits
for that one, which is the single highest-leverage line in this cluster
— fixing it makes five downstream sentinel checks correct without
touching any of them.

## `auto-merge-finalization.ts` (3) — one real but diagnostic-only, two
non-defects

`task.column === "done"` selects which **reason string** is reported;
both arms return `{ ok: false }`. So a renamed board is refused with the
generic `missing-merge-confirmation` instead of the specific
`done-without-merge-confirmation`. Real, and worth less than the
signature change required to fix it — the resolver two functions up
already computes `isCompleteColumn`, but this function does not receive
it.

The other two are **not** defects and it is worth saying so explicitly:
the `columnId === "done"` near the top is the resolver's documented
degraded fallback (the live arm calls `columnHasFlag`), and the
`step.status` comparison is a **step status**, not a column.

## The pattern across both passes

Of **6 files and 15 guards** audited: **2** were live defects worth
converting, **4** were sentinels or dead paths that would have *broken*
a renamed board if converted, and the rest were diagnostics or misfiled
step statuses.

That ratio is the argument for these notes existing. A file's census
count is an upper bound on convertible sites, not a work estimate — and
in this cluster the naive reading of the number would have made things
worse more often than better.

## Verification

- `pnpm test:gate` — 161 / 487 / 13 / 71 passed
- `pnpm lint` — clean
- `tsc --noEmit` (`@fusion/core`, `@fusion/engine`) — clean
- census `--strict` — exit 0, counts unchanged (that is the point)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-30 18:05:52 -07:00
committed by GitHub
parent 27501a53da
commit aacd18e847
3 changed files with 43 additions and 0 deletions

View File

@@ -504,6 +504,20 @@ export function getRunAuditEventsImpl(store: TaskStore, options: RunAuditEventFi
return rows.map((row) => store.rowToRunAuditEvent(row));
}
/*
FNXC:WorkflowLifecycleColumns 2026-07-31-02:45 (audited — DEAD SYNC PATH, do not convert):
The literal below would leak a merge-queue entry on a renamed board — a card leaving review would
never be dequeued — except that this function does not run in production.
It is the SQLite-mode twin. The live path is `dequeueMergeQueueOnColumnExitInTransaction`
(`async-merge-coordination.ts`), called from `moves.ts`, and it is ALREADY converted: it takes
`moveReviewColumns` and the caller supplies them. This body reaches for `store.db.prepare`, which
throws in PostgreSQL backend mode, so a renamed board never gets far enough to be mis-dequeued.
Converting it would mean threading a lane set into a function whose first statement cannot execute.
Recorded instead, so the census entry is not mistaken for unconverted debt — and so that whoever
finally deletes the sync SQLite residue can take this with it.
*/
export function dequeueMergeQueueOnColumnExitImpl(store: TaskStore, taskId: string, previousColumn: ColumnId, nextColumn: ColumnId, now: string): void {
if (previousColumn !== "in-review" || nextColumn === "in-review") {
return;

View File

@@ -422,6 +422,22 @@ export function isTaskArchivedImpl(store: TaskStore, id: string): boolean {
Sync isTaskArchived cannot query PostgreSQL. Prefer isTaskArchivedAsyncImpl
from async callers. In backend mode use the in-memory task cache when the
row is already hydrated; otherwise false (caller should have used async).
*/
/*
FNXC:WorkflowLifecycleColumns 2026-07-31-02:45 (audited — REAL, and narrow):
`cached.column` is a real board lane, so a renamed archived column is not recognised and this
sync check answers false for a card the board shows as archived.
Narrow because of what it already concedes: the note above says this path exists only for a row
that happens to be hydrated in the cache, and every async caller is told to use
`isTaskArchivedAsyncImpl` instead. The authoritative path (below) reads `getLiveTaskColumn`, whose
own comparison is the one worth converting — fixing it there makes this file's sentinel check
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
while `getLiveTaskColumn` still keys on the literal would leave the two disagreeing about what
archived means.
*/
const cached = store.taskCache.get(id);
return cached?.column === "archived";

View File

@@ -81,6 +81,19 @@ export async function validateWorkflowDoneMergeProof(
options: { result?: MergeResult; checkWorkflowSteps?: boolean } = {},
): Promise<WorkflowDoneMergeProofVerdict> {
const hasProof = hasDurableMergeProof(task, options.result);
/*
FNXC:WorkflowLifecycleColumns 2026-07-31-02:45 (audited — REAL but DIAGNOSTIC-ONLY):
This literal selects which REASON STRING is reported, not which branch runs. Both arms return
`{ ok: false }`, so on a renamed board a card sitting in the complete lane is refused with the
generic `missing-merge-confirmation` instead of the specific `done-without-merge-confirmation`.
Worth recording rather than converting from here: the resolver two functions up already computes
`isCompleteColumn` for exactly this workflow, and threading it in is the right fix — but this
function does not receive it, and widening the signature to improve an error string is a change
whose cost outweighs the diagnosis it sharpens. The other two census entries in this file are NOT
defects: the `columnId === "done"` at the top is the resolver's documented degraded fallback (the
live arm calls `columnHasFlag`), and the `step.status` comparison is a STEP status, not a column.
*/
if (!hasProof) return { ok: false, reason: task.column === "done" ? "done-without-merge-confirmation" : "missing-merge-confirmation" };
if (options.checkWorkflowSteps !== false && hasIncompleteWorkflowSteps(task)) {
return { ok: false, reason: "incomplete-workflow-steps" };