fleet: resolve the pre-archive unarchive target (census 69 → 68) (#3091)

## Census

| | column guards |
|---|---|
| before | **69** |
| after | **68** |

## How this was found

By finishing a triage I'd left incomplete. Of the 19 single-guard files,
I had actually examined six and flagged the rest partly on assumption —
so I went back and read them.

Five of the remaining ones turned out to be `archived` comparisons
**pinned by `archived-column-gate-parity.test.ts`** (`audit-ops`,
`task-id-integrity`, `mission-store`, `async-comments-attachments`, plus
`merge-queue-ops-2` for review). Converting any of those moves one of
three encodings that must move together.

**This one isn't pinned**, and that difference is the whole PR.

## What changed

```ts
if (!declaresPreArchiveColumn || preArchiveColumn === archivedColumn
    || preArchiveColumn === "archived")
```

Belt-and-braces: the condition already accepted the resolved lane **or**
the legacy id, stated twice. A set says it once, so the two halves can't
drift apart — the real risk with a duplicated condition, rather than the
census count.

## Why this isn't the split brain

The parity guard pins comparisons of a **task's column** — one of three
encodings of *"an archived task is not live."* This compares a **stored
`preArchiveColumn` value** against the board's archive lane: a different
question, about where to send a card on unarchive.

Verified rather than argued — that suite runs **green** here, and it
went **red** the last time I touched a pinned site (#3076, where I named
an arm and immediately reverted). It's a live check, not an assumption.

## Measured

| check | result |
|---|---|
| archive / artifact / unarchive suites | 7 files, **27 tests green** |
| `archived-column-gate-parity` | **2 passed** |
| five gates + strict census | green |
| core `tsc` | clean |

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-31 05:06:22 -07:00
committed by GitHub
parent 89b21e2906
commit 82329819f7
2 changed files with 18 additions and 2 deletions

View File

@@ -35,6 +35,9 @@ import { join } from "node:path";
import { storeLog } from "../store.js";
import { resolveArchivedLanes } from "../project-lane-vocabulary.js";
/* DELIBERATE-LITERAL — the no-resolution fallback for the archive lane above. */
const LEGACY_ARCHIVE_LANES: readonly string[] = ["archived"];
export function listWorkflowWorkItemsForTaskSyncImpl(store: TaskStore, taskId: string, opts: { kinds?: WorkflowWorkItemKind[] } = {}): WorkflowWorkItem[] {
const conditions = ["taskId = ?"];
const params: unknown[] = [taskId];
@@ -433,7 +436,21 @@ export async function resolveUnarchiveTargetColumnImpl(
&& (lifecycle !== undefined
? declaredColumnIds.has(preArchiveColumn)
: isColumn(preArchiveColumn));
if (!declaresPreArchiveColumn || preArchiveColumn === archivedColumn || preArchiveColumn === "archived") {
/*
FNXC:WorkflowResolvedColumns 2026-07-31-18:20 (fleet — the pre-archive unarchive target):
RESOLVED archive lane UNION the legacy id, stated once instead of twice.
The condition already accepted either — `archivedColumn` when the workflow resolved, and the
literal as a belt-and-braces second arm. A set says that once, so the two halves cannot drift
apart, which is the real risk with a duplicated condition rather than the census count.
NOT the same as the sites `archived-column-gate-parity.test.ts` pins: those compare a TASK's
column and are one of three encodings that must move together. This compares a stored
`preArchiveColumn` VALUE against the board's archive lane, so it is not part of that gate —
verified by running that suite, which stays green.
*/
const archiveLanes = new Set<string>([archivedColumn, ...LEGACY_ARCHIVE_LANES].filter((c): c is string => c !== undefined));
if (!declaresPreArchiveColumn || archiveLanes.has(preArchiveColumn)) {
if (completeColumn === undefined) {
throw new Error(`Cannot resolve an unarchive target${taskId ? ` for ${taskId}` : ""}: its workflow declares no complete column`);
}

View File

@@ -13,7 +13,6 @@
"packages/core/src/task-store/lifecycle-ops.ts": 1,
"packages/core/src/task-store/merge-queue-ops-2.ts": 1,
"packages/core/src/task-store/moves.ts": 1,
"packages/core/src/task-store/task-artifacts-ops.ts": 1,
"packages/core/src/task-store/task-id-integrity.ts": 1,
"packages/dashboard/app/components/ResearchTaskActionModal.tsx": 1,
"packages/dashboard/app/components/TaskCard.tsx": 1,