feat(gate): ratchet move-target literals at zero — #3150's population had nothing holding it (#3246)

Closes the gap I flagged when re-measuring #3150: that population is at
**0**, and nothing was holding it there.

## Why this surface has no gate today

The lifecycle census parses **comparisons**. A move destination is a
call **argument**:

```ts
await store.moveTask(id, "in-review");   // never counted by anything
```

#3150 measured 31 of these across four files. They are now 0 — I
verified that on current main before writing this — but the comparison
backlog drifted **787 → 854** during the window its own ratchet was
unwired, and this population never had one.

## The failure mode is louder than the guards'

A wrong lane **guard** silently answers "no". A wrong move **target** is
rejected by `moveTaskInternal` with `TransitionRejectionError:
unknown-column` — so on a board that renamed its review lane, every task
finishing implementation **threw** instead of reaching review. Loud at
runtime, invisible to any test on the default board.

## AST, not grep — and that is measured, not stylistic

| scan | result |
|---|---|
| comment-naive grep of `self-healing.ts` | 1 hit — **JSDoc prose**: `*
could call moveTask("in-review")` |
| #3150's own SQL survey by grep | 37 hits against **12** real sites (25
comments) |

Comments are not AST nodes, so that false-positive class cannot occur
here in either direction.

## Controls — all four run, because a gate that only reports 0 proves
nothing

| probe | expected | got |
|---|---|---|
| real `moveTask(id, "in-review")` injected | fail | **exit 1**, names
the file |
| identical call as JSDoc prose | pass | **exit 0** (AST ignores
comments) |
| legacy target + leading `DELIBERATE-LITERAL` | pass | **exit 0**
(marker honored) |
| probe removed | pass | **exit 0** |

The third is the #1411 `recoveryRehome` safe-landing path, where the
legacy id genuinely *is* the target. Marker must be **leading** — the
census already learned that an inline marker attaches to the wrong node
and is silently ignored.

## Ratchet semantics match the census

Fails on a **drop** as well as a rise. A stale allowance is a hole a
re-added target can return through while the gate stays green — exactly
what let the comparison baseline drift.

## Measured

| check | result |
|---|---|
| this gate | scans **1816** files, reports **0** |
| `check:lifecycle-columns` / `check:sql-column-literals` | 0 / 0 |
| `check:fnxc-future-dates` / `check:lane-wiring` | 0 / 0 |
| eslint / `pnpm test:gate` | clean / exit 0 |

Wired into `pr-checks.yml` beside the sibling ratchets, named to match
("Move-target ratchet").

## Scope

Gate only — **no production code touched**, and no conversions in this
PR. The population was already empty; this makes "31 → 0" an invariant
instead of a snapshot, which is the caveat I attached when recommending
#3150 for closure.

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

* **Quality Improvements**
  * Added automated validation for task-movement configuration values.
  * Pull request checks now detect unexpected changes in tracked values.
* Added baseline tracking with strict validation to identify both
additions and removals.
  * Added support for explicitly documenting intentional exceptions.
  * Improved reporting for file-discovery and source-reading failures.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-31 14:56:07 -07:00
committed by GitHub
parent 301bd8ed1e
commit 4e2f52ce8f
4 changed files with 166 additions and 0 deletions

View File

@@ -58,6 +58,8 @@ jobs:
run: pnpm check:lifecycle-columns
- name: SQL-column ratchet
run: pnpm check:sql-column-literals
- name: Move-target ratchet
run: pnpm check:move-target-literals
- name: Inert lane/flag seams
run: pnpm check:inert-flag-seams
- name: FNXC stamp dates