U12: DELIBERATE-LITERAL counts key on file AND column — closing the P1 left on merged #2661 (#2666)

Closes the P1 that was still open when #2661 merged.

## The hole

A per-file integer is offset **within a single file**: remove one
reviewed `todo` exemption, add an `in-review` one beside it, and the
number never moves. The fresh guard is invisible to the column counts
too, because deliberate findings are excluded from them — so `--strict`
goes green with a new lifecycle-column guard hiding inside an existing
marker.

Now keyed on **file AND column id**.

**Proven with the exact scenario:** swapping a marked `triage` for
`done` inside `TaskCard.tsx` leaves the per-file total unchanged and now
fails with

```
packages/dashboard/app/components/TaskCard.tsx (DELIBERATE-LITERAL: done): 0 -> 1
```

## The pattern worth naming

This is the **third** time this instrument has been defeated by an
aggregate:

| version | defeated by |
|---|---|
| repo-wide `totals.deliberate` | an addition in file A offset by a
removal in file B |
| per-file integer | an addition offset by a removal **in the same
file** |
| per-file per-column | — |

Each step narrows what can offset silently, and I walked into the next
one twice by fixing the *reported case* rather than the *shape*. Writing
it down because the same reflex will produce a fourth if someone adds
another aggregate here.

**The residual is deliberate, not an oversight:** a same-file
**same-column** swap still offsets. Two `todo` exemptions in one file
are interchangeable by definition, so there is nothing a reviewer could
act on. That is recorded at the site so the next person doesn't
rediscover it as a bug.

## Migration, again

The key **shape** changed (`file` → `file\0columnId`), which is the same
hazard as a missing field: comparing new keys against old reports every
existing marker as a fresh rise and pushes people to convert
already-reviewed literals. I hit it on the first run here — `TaskCard
(DELIBERATE-LITERAL: triage): 0 -> 2` — exactly as I did one shape
earlier in #2661.

Detected by the delimiter rather than a version field, since old keys
have none, and re-seeded on the next `--update-baseline`. 15 file+column
entries recorded.

## Verification

`pnpm lint` clean. `pnpm test:gate` green (10 / 158 / 487 / 71). `pnpm
check:lifecycle-columns` exits 0.

Independent of #2655; either order merges.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-30 01:02:25 -07:00
committed by GitHub
parent 1f149d21de
commit a6138abeff
3 changed files with 47 additions and 13 deletions

View File

@@ -303,7 +303,19 @@ export function summarize(findings) {
}
totals[finding.kind] += 1;
if (finding.kind === "deliberate") {
deliberateByFile.set(finding.file, (deliberateByFile.get(finding.file) ?? 0) + 1);
/*
FNXC:WorkflowLifecycleColumns 2026-07-31-10:00 (PR #2661 review — greptile P1, same class again):
Keyed by FILE **and COLUMN ID**, not a per-file integer. A per-file aggregate is offset within a
single file: remove one reviewed `todo` exemption, add a `in-review` one beside it, and the
number never moves — so a fresh guard hides inside an existing marker.
That is the third time this instrument has been defeated by an aggregate (repo total -> per file
-> per file per column). Each step narrows what can offset silently. The residual is a same-file
SAME-COLUMN swap, and that one is deliberate: two `todo` exemptions in one file are
interchangeable by definition, so there is nothing a reviewer could act on.
*/
const key = `${finding.file}\u0000${finding.columnId}`;
deliberateByFile.set(key, (deliberateByFile.get(key) ?? 0) + 1);
}
if (finding.kind !== "column") continue;
byColumnId[finding.columnId] = (byColumnId[finding.columnId] ?? 0) + 1;

View File

@@ -155,14 +155,21 @@
"plugins/fusion-plugin-reports/src/store/report-types.ts": 1
},
"deliberateByFile": {
"packages/dashboard/app/components/command-center/MissionControlPanel.tsx": 3,
"packages/dashboard/app/components/TaskCard.tsx": 3,
"packages/dashboard/app/components/TaskDetailModal.tsx": 3,
"packages/engine/src/hold-release.ts": 3,
"packages/dashboard/src/routes/register-task-workflow-routes.ts": 2,
"packages/dashboard/app/components/TaskContextMenu.tsx": 1,
"packages/dashboard/app/utils/columnRoles.ts": 1,
"packages/engine/src/triage.ts": 1
"packages/dashboard/app/components/TaskCard.tsx\u0000triage": 2,
"packages/dashboard/app/components/TaskDetailModal.tsx\u0000triage": 2,
"packages/dashboard/app/components/command-center/MissionControlPanel.tsx\u0000done": 1,
"packages/dashboard/app/components/command-center/MissionControlPanel.tsx\u0000in-review": 1,
"packages/dashboard/app/components/command-center/MissionControlPanel.tsx\u0000todo": 1,
"packages/dashboard/app/components/TaskCard.tsx\u0000todo": 1,
"packages/dashboard/app/components/TaskContextMenu.tsx\u0000triage": 1,
"packages/dashboard/app/components/TaskDetailModal.tsx\u0000todo": 1,
"packages/dashboard/app/utils/columnRoles.ts\u0000todo": 1,
"packages/dashboard/src/routes/register-task-workflow-routes.ts\u0000todo": 1,
"packages/dashboard/src/routes/register-task-workflow-routes.ts\u0000triage": 1,
"packages/engine/src/hold-release.ts\u0000archived": 1,
"packages/engine/src/hold-release.ts\u0000done": 1,
"packages/engine/src/hold-release.ts\u0000in-review": 1,
"packages/engine/src/triage.ts\u0000triage": 1
},
"properties": {
"query": 83,

View File

@@ -179,17 +179,32 @@ which is NOT the same as "every marked file had zero" — comparing against an a
every existing marker as a fresh rise and demand people convert literals that were already reviewed.
Seed it on the next `--update-baseline` instead, and start comparing once it is present.
*/
const deliberateTracked = baseline.deliberateByFile !== undefined;
/*
FNXC:WorkflowLifecycleColumns 2026-07-31-10:05:
The key SHAPE changed (file -> file\u0000columnId), and a shape change is the same migration hazard
as a missing field: comparing new keys against old ones reports every existing marker as a fresh
rise and demands people convert already-reviewed literals. I hit exactly that on the first run here
(`TaskCard (DELIBERATE-LITERAL: triage): 0 -> 2`), and hit the same wall one shape earlier in #2661.
Detect by the delimiter rather than by a version field: old keys have none. Re-seeds on the next
`--update-baseline`, then compares normally.
*/
const deliberateKeysAreCurrentShape = Object.keys(baseline.deliberateByFile ?? {}).every((k) => k.includes("\u0000"));
const deliberateTracked = baseline.deliberateByFile !== undefined && deliberateKeysAreCurrentShape;
const baselineDeliberateByFile = new Map(Object.entries(baseline.deliberateByFile ?? {}));
const currentDeliberateByFile = new Map(summary.deliberateByFile ?? []);
for (const [file, count] of deliberateTracked ? currentDeliberateByFile : []) {
const allowed = baselineDeliberateByFile.get(file) ?? 0;
if (count > allowed) regressions.push({ file: `${file} (DELIBERATE-LITERAL)`, count, allowed });
else if (count < allowed) stale.push({ file: `${file} (DELIBERATE-LITERAL)`, count, allowed });
// Keys are `file\u0000columnId`; render them readably in the report.
const [f, columnId] = file.split("\u0000");
const label = `${f} (DELIBERATE-LITERAL: ${columnId})`;
if (count > allowed) regressions.push({ file: label, count, allowed });
else if (count < allowed) stale.push({ file: label, count, allowed });
}
for (const [file, allowed] of deliberateTracked ? baselineDeliberateByFile : []) {
if (!currentDeliberateByFile.has(file) && allowed > 0) {
stale.push({ file: `${file} (DELIBERATE-LITERAL)`, count: 0, allowed });
const [f, columnId] = file.split("\u0000");
stale.push({ file: `${f} (DELIBERATE-LITERAL: ${columnId})`, count: 0, allowed });
}
}