Files
fusion/packages
gsxdsm 3066948110 fleet: async-comments-attachments.ts NOT converted — the archived gate lives in three encodings (52 sites), pinned by a guard that fails on each (#2724)
Claimed the **async-comments-attachments** cluster (9) and did not
convert it. This PR is the evidence for why, as a guard rather than a
note — **no production change, census unmoved.**

## What the cluster actually is

Every other file in the backlog converts on its own: resolve the task's
lifecycle columns, compare against the role. `archived` doesn't, and not
by a matter of degree.

**Measured in `packages/core` — one rule, three encodings, 52 sites:**

| encoding | sites | files | what it decides |
|---|---:|---:|---|
| TypeScript `=== "archived"` | **37** | 23 | what code does with a row
it already has |
| Drizzle `ne(tasks.column, 'archived')` | **7** | 6 | which rows a
query returns |
| raw `` sql`…column != 'archived'` `` | **8** | 5 | same — and
invisible to both scans above |

Convert only the TypeScript half and a board whose archived lane is
renamed **splits**: `getLiveTaskColumn` correctly reports the task
archived (it resolved the role) while `readLiveTaskRows` still hands it
back as live. A document write is rejected by its gate while its parent
is listed as live — a state neither gate alone can produce today.

And nothing would catch it. **Every builtin workflow spells that column
`archived`**, so all three encodings agree by accident on every board we
ship.

## Why the SQL sides aren't just converted too

`ne(tasks.column, ...)` needs the resolved id as a **query-build
value**, so the IR must be resolved *before* the query — including
inside the `for update` document/artifact transactions, which today
receive a `db`/`tx` handle and **no store and no workflow reader**. One
raw site is a hand-written `SELECT` string, so its comparison isn't even
a Drizzle expression that could take a bound value without rewriting the
query.

The two real options are: thread a resolver into the persistence layer,
or **declare `archived` a non-renameable system column** and mark all 52
sites deliberate. Both are decisions with blast radius. Neither is a
fleet conversion, so I didn't pick one.

## I was wrong about the shape, and that's the strongest part

I wrote the third case as an assertion that **no** raw `sql` template
compares a column to `'archived'` — I assumed two encodings. **It failed
on the first run with five files.**

Nothing in the repo was counting them: they aren't comparisons
(invisible to the column census) and aren't `eq`/`ne` calls (invisible
to the Drizzle scan). A partial conversion doesn't have to miss one
encoding — it can miss two. That case is now an inventory, with a note
on why asserting absence was the wrong invariant: an absence assertion
has to be deleted by whoever adds the next raw template, and deleting a
red guard is how a class of sites stops being tracked.

## Injection proof — all three run

| injected | result |
|---|---|
| convert one TS comparison in `async-comments-attachments.ts` | fails:
**"TypeScript encoding changed"** |
| remove one Drizzle predicate in `async-lifecycle.ts` | fails:
**"Drizzle encoding changed"** |
| remove one raw template in `reads.ts` | fails: **"Raw-sql encoding
changed"** |

Each failure carries the split-brain explanation and the two real
options, so the next worker hits the reason rather than a bare count
mismatch.

## Two scan bugs I fixed in my own guard

- **It audited documentation.** The raw-template scan reported three
sites in `async-archive-lineage.ts` that were prose in a JSDoc block.
Now comment-stripped through the census's shared `stripComments` rather
than a second implementation.
- **It missed half the cluster.** Requiring a receiver (`x.column ===
"archived"`) misses the list paths that hold the value in a bare local
(`if (column === null || column === "archived") return []`) — **four of
the eight sites in the target file**, measured. The matcher now accepts
a bare identifier named `column`.

Counts here are per file, not per line: line numbers churn on unrelated
edits, and a ratchet that cries wolf gets deleted.

## Verification

`pnpm test:gate` **GREEN** (158 + 10 + 487 + 71) · new guard **2/2** ·
`pnpm lint` clean · core `tsc` clean · census `--strict` exits 0 with
the **baseline untouched** — this PR converts nothing and claims
nothing.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 07:08:28 -07:00
..
2026-07-26 18:11:47 -07:00