Skip to content

fix: main is red on the lifecycle ratchet — re-record the census baseline - #2811

Merged
gsxdsm merged 2 commits into
mainfrom
fix/red-main-census-baseline
Jul 30, 2026
Merged

fix: main is red on the lifecycle ratchet — re-record the census baseline#2811
gsxdsm merged 2 commits into
mainfrom
fix/red-main-census-baseline

Conversation

@gsxdsm

@gsxdsm gsxdsm commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

main is RED on the lifecycle ratchet right now. node scripts/lifecycle-column-census.mjs --strict exits 1 on pristine origin/main, which is the Lint job's Lifecycle-column ratchet step — so every open PR fails Lint until this lands, regardless of its own contents.

Verified on a detached checkout of origin/main, not on a branch of mine.

Cause

Eight DELIBERATE-LITERAL markers were added across seven files without re-recording the baseline:

packages/core/src/task-move-disposer.ts            (in-progress, todo)
packages/core/src/task-store/archive-lifecycle-2.ts (archived)
packages/dashboard/src/github-tracking-comments.ts  (done)
packages/dashboard/src/gitlab-tracking-comments.ts  (in-progress)
packages/dashboard/src/server.ts                    (archived)
packages/dashboard/src/task-planner-chat-context.ts (done)
packages/dashboard/src/test/mockCoreEngine.ts       (in-review)

Adding a marker RECLASSIFIES a site (column-guard → deliberate), so the tracked deliberate totals move and --strict fails until the baseline records the new shape. It is the same mechanism that turned #2775 red earlier today — a marker landing without its baseline — which is worth noting because it has now happened twice from different PRs.

The fix

Baseline re-recorded, nothing else. Zero source changes; the diff is one derived file.

  • --strict exits 0
  • pnpm test:gate161 / 13 / 487 / 71
  • pnpm lint clean

Worth a follow-up by whoever owns the ratchet

The failure is structural rather than careless: a PR that adds a marker is doing the right thing, and the baseline requirement is only discovered when CI goes red — after merge, for everyone else. Two options, neither of which I am taking unilaterally on a red-main fix:

  1. have --strict treat a marker-only reclassification as an accepted rise (it is not new debt — the count of unconverted guards goes down);
  2. or fail the PR that adds the marker, by comparing against the base ref rather than the recorded baseline — the machinery for that already exists in this script.

I would take (1): a marker is the documented way to close a site, and requiring a second mechanical step to record it is a trap that catches good behaviour.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved lifecycle census error messages to distinguish genuine increases in column-guard debt from reclassified deliberate literals.
    • Added clearer remediation guidance for reclassified results, including when to update the baseline.
    • Updated lifecycle census baseline mappings to reflect current classifications.
  • Tests

    • Added coverage for unchanged baselines, genuine guard-count increases, and marker-only reclassification scenarios.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@gsxdsm, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d532608e-ea77-40f3-a88c-4672d4c43b96

📥 Commits

Reviewing files that changed from the base of the PR and between b8da829 and 6fe1f6c.

📒 Files selected for processing (2)
  • packages/engine/src/__tests__/census-reclassification-message.test.ts
  • scripts/lifecycle-column-census.mjs
📝 Walkthrough

Walkthrough

The lifecycle census now classifies deliberate-literal increases as reclassifications when guard counts do not rise, reports them differently in strict mode, updates baseline mappings, and adds tests for reclassification, genuine guard increases, and unchanged baselines.

Changes

Lifecycle census ratchet

Layer / File(s) Summary
Strict regression classification
scripts/lifecycle-column-census.mjs
Strict checks record whether deliberate-literal increases correspond to reduced guard counts and emit distinct reclassification or guard-increase messages with updated remediation guidance.
Baseline mapping updates
scripts/lib/lifecycle-column-census-baseline.json
Baseline byFile and deliberateByFile mappings are updated for core and dashboard lifecycle columns.
Behavioral regression tests
packages/engine/src/__tests__/census-reclassification-message.test.ts
Tests cover marker-only reclassification wording, genuine guard-count increases, and successful execution with the unchanged baseline.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the baseline re-recording that unblocks the lifecycle ratchet on main.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/red-main-census-baseline

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR refines lifecycle-census diagnostics and adds regression coverage.

  • Distinguishes deliberate-literal reclassifications from ordinary guard-count rises.
  • Provides baseline re-recording guidance for marker-only changes.
  • Adds tests for reclassification, genuine growth, and an unchanged baseline.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
scripts/lifecycle-column-census.mjs Classifies deliberate-count changes and selects more specific strict-mode diagnostics.
packages/engine/src/tests/census-reclassification-message.test.ts Adds subprocess coverage for unchanged baselines, deliberate reclassification, and genuine guard growth.

Reviews (4): Last reviewed commit: "fix(census): "did NOT increase", not "we..." | Re-trigger Greptile

@gsxdsm

gsxdsm commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Added the follow-up I flagged: the failure now says what actually happened

Second commit on this PR. The first unblocks main; this stops the next occurrence from being a mystery.

The message was actively misleading. column-guard count ROSE is the opposite of what a marker-only change does — unconverted debt goes down. On a red main, that sent every reader hunting for a regression that does not exist. Main hit this twice in one day from two different PRs, and both times the text pointed away from the fix.

Now a marker-only diff reports:

lifecycle-column-census --strict: sites were RECLASSIFIED as DELIBERATE-LITERAL
  packages/dashboard/src/server.ts (DELIBERATE-LITERAL: archived) (reclassified, not new debt): 0 -> 1
  …
Unconverted debt went DOWN — a marker moved these sites out of the guard count.
The baseline records both totals, so it must be re-recorded in the same change:

  node scripts/lifecycle-column-census.mjs --strict --update-baseline

This does not loosen the ratchet. The baseline still must be re-recorded and the run still exits 1. Whether a marker-only change should fail at all is a policy question for the ratchet's owner — I raised both options on the first commit and am not deciding it here. Making an existing failure legible is not a policy change.

Two bugs in my own detection, caught by its test before it shipped

Both would have made the guard silently never fire — the exact failure mode this program exists to find, in the code meant to find it:

  1. deliberateByFile is keyed file\0columnId, byFile by plain path. My first lookup used the suffixed key against the plain map and always read 0.
  2. Then I asked "did the guard count FALL by at least the marker rise". It usually cannot: a file taken to zero guards loses its byFile entry entirely, so both sides read 0 and no fall is observable at check time — the fall happened in an earlier re-record.

The honest condition is weaker and correct: markers rose and guards did not. It cannot mask real regrowth, because a file whose guard count also rose is still reported as a rise — asserted by a case that keeps the ROSE wording for a genuine regression.

3 tests: the reclassification message, the genuine-rise message, and a control proving the whole thing still passes against the unmodified baseline. The control matters here because the suite shells out to the real script; without it every assertion could pass on a script that always fails.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/lifecycle-column-census.mjs (1)

352-362: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct and shorten the duplicated FNXC records.

Both comments are dated 2026-07-31, which is in the future relative to July 30, 2026, and duplicate lengthy narrative. Retain a concise invariant/rationale with a current timestamp.

  • scripts/lifecycle-column-census.mjs#L352-L362: replace the future-dated multi-paragraph FNXC block.
  • packages/engine/src/__tests__/census-reclassification-message.test.ts#L1-L23: keep the test-focused FNXC record concise and current.

As per coding guidelines, “Add concise FNXC comments … with a timestamp in yyyy-MM-dd-hh:mm”; retrieved learnings also require current, accurate timestamps.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/lifecycle-column-census.mjs` around lines 352 - 362, Replace the
duplicated future-dated FNXC comments at scripts/lifecycle-column-census.mjs
lines 352-362 and
packages/engine/src/__tests__/census-reclassification-message.test.ts lines 1-23
with concise, accurate invariant/rationale comments using the current timestamp
in yyyy-MM-dd-hh:mm format; preserve the explanation relevant to the census
reclassification message and test behavior at each site.

Sources: Coding guidelines, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/lifecycle-column-census.mjs`:
- Around line 506-525: Update scripts/lifecycle-column-census.mjs lines 506-525
so the reclassification tag and remediation message describe guard counts that
did not increase, including equality, rather than claiming debt went down or
calling it not new debt; retain the baseline update command. Update
packages/engine/src/__tests__/census-reclassification-message.test.ts lines
62-79 to assert the accurate equality-case wording.

---

Nitpick comments:
In `@scripts/lifecycle-column-census.mjs`:
- Around line 352-362: Replace the duplicated future-dated FNXC comments at
scripts/lifecycle-column-census.mjs lines 352-362 and
packages/engine/src/__tests__/census-reclassification-message.test.ts lines 1-23
with concise, accurate invariant/rationale comments using the current timestamp
in yyyy-MM-dd-hh:mm format; preserve the explanation relevant to the census
reclassification message and test behavior at each site.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b253a1f6-3041-4b28-8894-ebb386f1751e

📥 Commits

Reviewing files that changed from the base of the PR and between b728857 and b8da829.

📒 Files selected for processing (3)
  • packages/engine/src/__tests__/census-reclassification-message.test.ts
  • scripts/lib/lifecycle-column-census-baseline.json
  • scripts/lifecycle-column-census.mjs

Comment thread scripts/lifecycle-column-census.mjs
gsxdsm added a commit that referenced this pull request Jul 30, 2026
…is the common one

#2811 review. `reclassified` is `guardsNow <= guardsBefore`, so it is TRUE when the guard count is
UNCHANGED — which is exactly what adding a DELIBERATE-LITERAL marker to a site the parser already
excluded produces, and exactly what the fixture in this PR's own test creates.

Claiming a decrease there puts a second wrong number in a message whose entire purpose is to stop
the reader chasing one.

The test's comment was wrong in the same way — it said the fixture makes "the same file's guard
count fall", when deleting only deliberate entries leaves it unchanged. Corrected, and the test now
also asserts the decrease claim is absent.

Mutation-verified: restoring "went DOWN" fails the case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gsxdsm

gsxdsm commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Real, and the equality case is the common one — fixed in 3c41669.

reclassified is guardsNow <= guardsBefore, so it is TRUE when the guard count is unchanged. That is exactly what adding a DELIBERATE-LITERAL marker to a site the parser already excludes produces, and exactly what this PR's own fixture creates by deleting only deliberate entries. So the branch that fires most often was claiming a decrease that had not happened — a second wrong number in a message whose entire purpose is to stop the reader chasing one.

Now reads "Unconverted debt did NOT increase", which is accurate for both < and =.

The test's comment was wrong in the same way, which is why the assertion looked right: it said the fixture makes "the same file's guard count fall". It does not — deleting deliberate entries leaves the count untouched. Corrected, and the test now also asserts the decrease claim is absent, so the wording cannot drift back. Mutation-verified: restoring "went DOWN" fails it.


A correction I owe this PR. I initially measured --strict on this branch, got exit 1, and was about to report that #2811 does not actually clear the red main. That was wrong, and the cause was mine: an earlier cross-branch git checkout origin/main -- scripts/lib/lifecycle-column-census-baseline.json had left the file staged in my index, so I was measuring main's baseline against this branch's tree.

On a clean checkout of this branch: --strict exits 0 and census-reclassification-message.test.ts passes 3/3. This PR does clear the red.

I have also closed #2813, which I had opened for the same red main before finding this one. Two PRs editing the same ratchet file would conflict and risk one silently reverting the other's entries — precisely the failure a ratchet exists to prevent. #2811 is the better change: it fixes the misleading message and moves --update-baseline above the rise exit so a reclassified file can actually be re-recorded, which #2813 did not do.

One thing from #2813 worth carrying regardless: pnpm test:gate does not run the census strict check. Every gate run on the PR that turned main red (#2783, mine) was green. The check lives in census-baseline-corruption-guard.test.ts, outside the engine-core allow-list, so nothing in the merge path exercises it.

@gsxdsm

gsxdsm commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Second query-class conversion, and it needed TWO fixes — the second only visible once the first landed

handoff/stale-task-reporter-query-lanes (supersedes handoff/query-class-first-conversion; it carries the shared helper too). Gate 161 / 13 / 487 / 71, lint clean, --strict exits 0.

stale-task-reporter.ts has a census count of ZERO — no lifecycle comparison anywhere — and was completely inert on a renamed board. Both listTasks({ column }) reads returned empty, so no stale-task signal was ever raised, on exactly the board where work is most likely sitting unnoticed.

The finding: fixing the query moved the failure one frame deeper

With the query widened, the case still reported zero. getTaskAgeStalenessSignal takes an optional lifecycle and defaults to the legacy pair, so a card the query now returned was refused inside the signal.

Converting only the query would have looked like a fix and changed nothingsurfaced: 0 before and after. My test caught it only because it asserts the OUTCOME rather than the query argument. That is worth weighing against #2800's choice to assert the argument: asserting the argument is right when you are pinning a known defect (the outcome is 0 either way), and wrong when you are proving a fix, because the outcome is the only thing that distinguishes a real conversion from a deeper one.

The two halves need different shapes, which is the reusable point:

  • the read is project-wide (resolveProjectColumnsForRoles) — there is no task in hand yet;
  • the per-card signal is per-task (resolveTaskLifecycleColumns) — a board spans workflows.

Using the project union for the per-card question would have marked a card stale because another workflow calls its column wip. That is the flat-set mistake this program has made four times, and the helper's own header warns against exactly this.

A test premise of mine that was simply wrong

I first asserted that a card in in-progress is surfaced on the RENAMED board, reasoning that the query unions the legacy ids. The query does — but the per-task signal then correctly refuses it, because that card's own workflow does not call in-progress a wip lane. The product was right and my premise was wrong.

What the union actually buys is that the row is fetched; whether it is stale is then a per-task question. The case now covers the guarantee that genuinely matters — a store with no workflow selection, where both halves fall back together.

That is the fourth fixture/premise error I have made in this sweep (createdAt epoch-ms, not.toHaveBeenCalled() leakage, the running-gate testId, and this). All four were caught by running the test, none by reading the code. The rule I would hand on: when a new case fails, the fixture and the premise are the likelier culprits than the product — my hit rate on "the product is wrong" has been about one in five.

Verification

7 cases in the reporter suite, 8 on the shared helper. Revert proof: restoring either literal read makes the renamed case surface zero.

Still not done

self-healing.ts's 49 — actively owned and mid-conversion. project-engine.ts (7), restart-recovery-coordinator.ts (2), executor.ts (2). Each needs its own read of what the sweep does with the rows, and this conversion is the reason why: the query is rarely the whole defect.

gsxdsm and others added 2 commits July 30, 2026 12:28
… count ROSE'

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…is the common one

#2811 review. `reclassified` is `guardsNow <= guardsBefore`, so it is TRUE when the guard count is
UNCHANGED — which is exactly what adding a DELIBERATE-LITERAL marker to a site the parser already
excluded produces, and exactly what the fixture in this PR's own test creates.

Claiming a decrease there puts a second wrong number in a message whose entire purpose is to stop
the reader chasing one.

The test's comment was wrong in the same way — it said the fixture makes "the same file's guard
count fall", when deleting only deliberate entries leaves it unchanged. Corrected, and the test now
also asserts the decrease claim is absent.

Mutation-verified: restoring "went DOWN" fails the case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gsxdsm
gsxdsm force-pushed the fix/red-main-census-baseline branch from 3c41669 to 6fe1f6c Compare July 30, 2026 19:29
@gsxdsm

gsxdsm commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased — main is green again, so this PR is now purely the message fix

The baseline re-record dropped out on rebase: main picked up the same fix from another PR while this sat in review. That is the right outcome and I am not re-adding it — two identical baseline re-records would be churn, and the urgent half of this PR has served its purpose.

What remains is the part that stops the recurrence: a marker-only diff now says RECLASSIFIED and prints the recovery command, instead of announcing a column-guard count ROSE that did not happen. main went red on this twice in one day from two different PRs, and both times the failure text pointed away from the fix.

Verified after the rebase: --strict exits 0, guard suite 3 passed, gate 161 / 13 / 487 / 71, lint clean.

On the wording correction that landed here

Someone pushed "did NOT increase", not "went DOWN" — the equality case is the common one onto this branch, and it is a straight catch on my own text. My condition is guardsNow <= guardsBefore, which includes equality — a marker added to a site that was already outside the guard count leaves debt unchanged, and that is the common case, not a decrease. I asserted a decrease the code does not establish.

Worth naming because it is the same error class this PR exists to fix, one level up: a diagnostic that overstates what it knows is still a misleading diagnostic. I replaced "ROSE" (wrong direction) with "went DOWN" (also unverified) — the correction replaced it with what the condition actually proves.

Scope note

I have deliberately not re-expanded this PR. The query-class conversions I have been working on live on their own branches (handoff/stale-task-reporter-query-lanes, which carries the shared resolveProjectColumnsForRoles helper plus two worked conversions), because bundling tooling fixes with product conversions is what made the last three review rounds on agent-assignment necessary.

@gsxdsm

gsxdsm commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Third query-class conversion: restart recovery did not run at all on a renamed board

handoff/restart-recovery-query-lanes (carries the shared helper). Gate 161 / 13 / 487 / 71, lint clean, --strict exits 0.

recoverInterruptedRuns carried a note I had written earlier flagging this as the query class and declining to convert it. The note was right about the diagnosis: the listTasks({ column }) QUERY was the live filter and the .filter beneath it a redundant re-assertion, so converting the predicate alone would have dropped a census count and changed nothing. On a renamed board the sweep never ran — an engine restart left interrupted tasks stuck with no requeue.

Three layers, and naming them is the point

The previous two conversions in this class each hid a second layer behind the first, so I looked for all of them before touching anything:

  1. The query — fixed, project-level (resolveProjectColumnsForRoles), because no task is in hand before the read.
  2. The redundant .filterdeleted, not converted. Re-asserting the column the query just selected on adds nothing, and a second copy of a rule is how a read and its filter drift apart. A test pins the one thing it did contribute: the paused guard.
  3. The move destination — already resolved via resolveReboundTargetForTask. Only its comment was stale, still warning about a hardcoded todo long after the fix landed. Corrected in place rather than deleted, because the reason it matters is still true.

Layer 3 is worth calling out separately: a stale warning comment is its own hazard. It told the next reader a defect existed where none did, and I nearly re-fixed it before checking the call.

A mistake I made building this

I pulled packages/core/src/index.ts wholesale from my own handoff branch to get the helper export, and clobbered two exports main had added since (resolveReboundTargetForTask, resolveWipTargetForTask) — three packages stopped compiling. Restored from main and re-applied only my one line.

Same root cause as the missing-test-file slip earlier: taking a file from another branch takes its whole contents, including whatever is now out of date. For a barrel file that is nearly always wrong; the fix is to re-apply the single edit on top of the current version. tsc caught it immediately, which is why the branch is green — but it would have been a broken build for everyone if the barrel had not been type-checked.

Verification

12 cases in the suite (2 new), revert-proven: restoring the literal query requeues nothing on the renamed board.

Remaining in this class

self-healing.ts (49, actively owned), project-engine.ts (7), executor.ts (2). Three worked examples now exist — backlog-pressure-reporter, stale-task-reporter, and this one — and the pattern is stable: resolve the roles, iterate the set, dedupe by id, then check whether a per-task guard downstream needs the per-task resolver too.

@gsxdsm
gsxdsm merged commit 2ccd78a into main Jul 30, 2026
7 checks passed
@gsxdsm
gsxdsm deleted the fix/red-main-census-baseline branch July 30, 2026 19:40
gsxdsm added a commit that referenced this pull request Jul 30, 2026
…ert (#2825)

## Red on main

```
lifecycle-column-census > the baseline can always be re-recorded
  > writes the baseline BEFORE the rise check can exit
AssertionError: expected 19345 to be greater than 26374
```

Read literally, that says the CLI now runs its rise check *before* the
`--update-baseline` write — which would break the one command whose
entire job is re-recording, and would be a genuine bug worth stopping
for.

**It does not.** The order in code is correct and unchanged:

| | line |
|---|---|
| `if (updateBaseline) { … writeBaseline() … process.exit(0)` |
`scripts/lifecycle-column-census.mjs:487` |
| `"column-guard count ROSE"` + `process.exit(1)` | `:510` |

## What actually moved was a comment

Line **359** explains this exact failure mode and quotes the marker
verbatim:

> …`"column-guard count ROSE"`, which is the opposite of what happened
and sends the reader looking for…

So `cli.indexOf("column-guard count ROSE")` found the **prose**, 7000
characters before the branch it was meant to locate.

A guard that a comment can invert is not measuring control flow. And the
honest-looking fix — reword the comment — silently re-arms the same trap
for whoever explains this next.

`cliSource()` now strips comments before indexing. The same defence is
already used by `archived-column-gate-parity.test.ts`, for the same
reason: notes documenting *why* a literal is dangerous have to mention
the literal.

## Kept, not deleted

The end-to-end block below these does cover the contract — it drives the
real CLI and asserts exit code, baseline content and printed output, and
its own comment names the ordering bug. It would have been defensible to
delete the two source-text cases as redundant.

I kept them because two guards at different levels is the point: **e2e
proves the behaviour, these locate the branch that provides it.** They
only needed to stop being defeated by prose.

## Evidence

The real ordering bug — make a rise exit before the update branch writes
— fires **all three**:

| guard | failure |
|---|---|
| source order | `expected 9454 to be greater than 9505` |
| slice / uniqueness | `expected 10433 to be -1` |
| end-to-end | `expected 1 to be +0` (exit code) |

**My first mutation attempt was invalid** and I nearly reported it as
evidence: it moved the block by line range, mangled the file, and both
markers disappeared — the resulting `-1`s look like a firing guard but
prove nothing. A mutation that corrupts its target is not evidence that
a guard works.

Engine **10991 passed / 0 failed** · gate **732 green** · lint clean.
Test-only; the CLI is restored clean.

## Note on duplicated effort

#2811 and my #2814 both re-recorded the census baseline for #2783's
rise, concurrently. No harm done — but this file is now a fleet-wide
contention point, and the per-file baseline shape exists precisely to
avoid that. Worth one owner for census/ratchet fixes rather than whoever
notices first.

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

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
gsxdsm added a commit that referenced this pull request Jul 30, 2026
…#2818)

Three sweeps that **never ran at all** on a renamed board, plus the
shared answer the rest of the class needs. Consolidated from three
handoff branches so the helper appears once. #2811 merged, so this is my
only open PR.

`#2800` measured this class and shipped evidence deliberately without
conversions: `listTasks({ column: "<literal>" })` filters in the store,
so on a renamed board the read returns an **empty array** and the sweep
it feeds does nothing. The census scores the comparison *inside* the
loop, never the query above it.

## What was broken

| file | census count | what actually happened on a renamed board |
|---|---|---|
| `backlog-pressure-reporter.ts` | **0** | both reads empty, ratio
computed as 0/0 — **the alert never fired**, on a board that may be
under exactly the pressure it reports |
| `stale-task-reporter.ts` | **0** | both reads empty — **no stale-task
signal ever raised**, where work is most likely sitting unnoticed |
| `restart-recovery-coordinator.ts` | flagged | sweep never ran — **an
engine restart left interrupted tasks stuck with no requeue** |

Two of the three have a census count of **zero**. They contain no
lifecycle comparison at all, so they have never appeared in the backlog,
in a per-file list, or in any "N → 0" claim — and were completely inert.
**A file at zero is not evidence of anything.**

## The shared answer, and what it is not

Every existing resolver answers a **per-task** question. A query has no
task in hand, so it needs the project-level one: every column any
workflow declares for a role, unioned with the legacy ids so a board
mid-rename still finds rows under the old ones. The set is never empty,
so a caller cannot accidentally query nothing.

The header states what it is **not**: answering a per-card question from
the union would mark a card as review because some *other* workflow
calls its column review — the flat-set mistake this program has made
four times.

## The finding that generalises: the query is rarely the whole defect

`stale-task-reporter` **still reported zero after the query was fixed**
— `getTaskAgeStalenessSignal` defaults to the legacy pair, so a card the
query now returned was refused inside the signal. Converting only the
query would have looked like a fix and changed nothing.

That is a caveat on #2800's approach, offered as refinement rather than
correction: **asserting the query ARGUMENT is right when pinning a known
defect** (the outcome is 0 either way) **and insufficient when proving a
fix**, because the outcome is the only thing that distinguishes a real
conversion from a deeper one. All three conversions here assert
outcomes.

`restart-recovery` had three layers — query, a redundant re-assertion
(deleted; a test pins the `paused` guard it did contribute), and a move
destination that was **already** resolved but whose warning comment was
stale. A stale warning is its own hazard: it told the next reader a
defect existed where none did.

## Verification

- helper **8 passed** · three reporter/coordinator suites **29 passed**
- `pnpm test:gate` **161 / 13 / 487 / 71** · lint clean · `--strict`
exits 0 · four `tsc` targets clean
- each conversion revert-proven independently; the failing case is named
in each test header

## Two mistakes worth recording

**The helper's own test caught a bug in it.** My first draft wrapped the
definition loop in one `try`, and `parseWorkflowIr` **validates** rather
than parses — one malformed row would have returned legacy-only lanes
for *every* workflow, indistinguishable from the bug it exists to fix.
Now isolated per definition.

**I clobbered the core barrel** by taking `index.ts` wholesale from a
handoff branch, dropping two exports `main` had added since; three
packages stopped compiling. Taking a file from another branch takes its
whole contents, including what is now stale — for a barrel that is
nearly always wrong. Re-applied as a single edit on top of `main`.

## Not included

`self-healing.ts`'s 49 — actively owned and mid-conversion; an outside
refactor there produces conflicting halves of one sweep.
`project-engine.ts` (7) and `executor.ts` (2) need their own read of
what each sweep does with the rows, which these three are the argument
for.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
gsxdsm added a commit that referenced this pull request Jul 30, 2026
…conversion (#2856)

## This is the cause of four main reds today, not a fifth instance of
them

I have now fixed the census baseline on `main` three times (#2814, plus
a withdrawn branch, plus watching #2811 and #2844 do the same). Rather
than do it a fourth time, here is why it keeps happening.

`census-baseline-corruption-guard` asserted:

```ts
expect(result).toContain("every file matches its baseline exactly");
```

That demands the **committed baseline be byte-in-step with the tree at
all times**.

**It is not, by design.** A conversion PR that removes guards leaves the
tree holding *fewer* than the baseline allows, and the CLI treats that
as the good case — it tightens the pin and exits 0. Measured directly:

```
simulated drop → EXIT ON DROP: 0
  "The baseline file has been rewritten downward. COMMIT IT …
   in CI this write is discarded with the runner, which is why the gate is green and not silent."
```

So the ratchet was already happy while this test went red. Every
conversion that did not *also* re-record the baseline turned `main` red
for a condition that was never a defect.

That is the mechanism behind **#2783's markers, #2837's query split and
two more** — plus three collisions between workers racing to re-record
the same file (#2811/#2814, #2844, and a branch of mine I deleted rather
than open as a duplicate).

## The fix matches the guard's own stated intent

Its comment says: *"a guard that always fails is no guard"* — its job is
to prove the **corruption** diagnosis does not false-positive on a
healthy file. **A tightened baseline is healthy.** So it now asserts
what that needs:

- the run **succeeds** — `execFileSync` throws on a non-zero exit, so a
**rise still fails before any assertion runs**; a rise is real debt and
must stay loud
- the corruption diagnosis is **absent**
- the outcome is one of the two healthy shapes the CLI can report

## Measured discrimination — all four cases

| scenario | before | after |
|---|---|---|
| **drop** (legitimate conversion) | ❌ 1 failed — *the false red* | ✅ 3
passed |
| **rise** (real debt) | ❌ fails | ❌ **still fails** |
| **corrupt JSON** | ❌ fails | ❌ still fails (case unchanged) |
| **unreadable file** | ❌ fails | ❌ still fails (case unchanged) |

Only *"somebody converted guards and has not re-recorded the pin yet"*
stops being a red.

## Scope

Engine **11022 passed / 0 failed** · gate **732 green** · lint clean.
Test-only.

**Does not change** the CLI, the ratchet, or what `--strict` reports.
Re-recording the baseline on a drop is still the right thing to do — it
just stops being an emergency that reddens main and blocks everyone else
while three people race to fix it.

The baseline file is restored byte-clean after every simulation above
(`git diff` verified).

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

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

## Summary by CodeRabbit

- **Tests**
- Expanded baseline validation coverage to detect unreadable or invalid
baseline data.
- Added support for both exact baseline matches and successfully
tightened baselines.
  - Improved health checks for baseline verification outcomes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant