notification: record the measured reason four wedge-progress ids stay literal, and un-red main's gate (#2882)

Two small things, neither of which changes behaviour.

## 1. A conversion I attempted, measured, and reverted

`hasProgressed` in the wedge-episode path names four column ids
outright. I converted them to a resolved lane set. It **broke an
existing gate test** — `task-wedge-notification.test.ts` → *"sends one
actionable push and mailbox message per active terminal episode"*: 1
message delivered, 2 expected.

The note already in that file was right, and stronger than it read. The
hazard is **not** specific to the resolve/claim ordering — it is **any
`await` added before the resolve**. Column resolution needs one.
`task:updated` listeners fire synchronously, so a re-wedge arriving
close behind a recovery reaches `claim` while the first episode is still
open, and the operator's second alert is dropped.

Product change reverted; only the comment lands, now carrying the
measurement and naming the failing test as the acceptance check for
whoever owns the wedge-episode contract. **Left counted, not exempted**
— the census should keep pointing here.

Worth stating: the pre-existing note was a warning written
speculatively. Attempting the conversion is what turned it into
evidence, and the evidence says the blocker is real but sits somewhere
else (per-task serialisation) than the note implied.

## 2. `main`'s gate is red, and not from this branch

`pnpm test:gate` fails on a clean `origin/main` tree at
`check-sql-column-literals`:

```
packages/core/src/team-analytics.ts: 3 site(s) now, baseline still allows 6 — re-record it
```

A reduction landed without re-recording the baseline in the same commit,
which that check explicitly asks for. Reproduced on `origin/main` with
my changes stashed, so it is not mine — but it blocks **every** open PR
until recorded. Ratchets **31 → 28** sites across 14 files, downward
only.

## The vacuous assertion this round (sixth)

The first version of the reverted test passed **with the fix reverted**.
`hasProgressed` is a three-clause OR, and the middle clause — *status is
a string and is not `failed`* — is true for a recovered task on any
board, so `status: "in-progress"` in the fixture satisfied it regardless
of column. Same shape as the other five: something the code does anyway.
Found by running the revert, not by reading it.

## Verification

`pnpm test:gate` 161 + 487 + 13 + 71 (green only with the baseline
commit); `tsc` engine clean; notification suite 77 passed; `pnpm lint`
and census `--strict` clean.
This commit is contained in:
gsxdsm
2026-07-30 16:59:03 -07:00
committed by GitHub
parent 890e1f87e7
commit cfb713bda1

View File

@@ -529,6 +529,21 @@ export class NotificationService {
Left COUNTED with no exemption marker — four of this file's five remaining entries are here, and the
census should keep saying so.
FNXC:WorkflowResolvedColumns 2026-07-31-02:40 (ATTEMPTED, MEASURED, REVERTED — do not retry as written):
I converted these four ids to a resolved `progressedLanes` set and it broke an existing gate test
(`task-wedge-notification.test.ts` -> "sends one actionable push and mailbox message per active
terminal episode": 1 message delivered, 2 expected).
The cause is the paragraph directly above, and it is stronger than it reads: the hazard is not
specific to the resolve/claim ordering, it is ANY await added before the resolve. Column resolution
needs one, so a resolved answer here costs a dropped operator notification whenever a re-wedge
arrives close behind a recovery. The `task:updated` listeners fire synchronously, so the second
emit reaches `claim` while the first episode is still open.
This is therefore blocked on serialising wedge handling per task, NOT on the conversion being hard.
Convert these four only in a change that already owns the wedge-episode contract, and re-run that
test as the acceptance check — it fails loudly, which is why this is recorded rather than exempted.
*/
const hasProgressed = task.column === "todo" || task.column === "in-progress" || task.column === "done" || task.column === "archived"
|| (!isActiveSelfHealingNoAction && typeof task.status === "string" && task.status !== "failed")