fix(review-gate): say why an unrecognized ledger verb held a finding - #1577
Conversation
|
@ally please review. Context: Flagging it explicitly because this PR changes that gate's reasoning, and its sibling #1568 needed three rounds — each one a real defect, including a partial-ledger false-green I would otherwise have merged. |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 779ee7b
Critical Issues (0)
Important Issues (1)
- [native-codex]
server/src/services/pr-comment-review-gate.ts:257-259— an unknown verb is reported even when a knownstill-presentledger entry for the same finding also exists, so the reason can falsely attribute the blocker to vocabulary drift rather than the explicit blocking disposition.- Only include an unrecognized verb when no applicable
blocksentry names that finding (or otherwise give the known blocking disposition precedence), and add a regression test covering both entries for one finding.
- Only include an unrecognized verb when no applicable
Suggestions (0)
Strengths
- The classified ledger extractor keeps retirement, blocking, and unrecognized verbs explicit and prevents unknown verbs from failing open.
- The diagnostic reuses the same finding matcher as retirement and bounds the status reason to GitHub's 140-character limit.
- The added tests cover unrelated findings and partial ledgers, and the live CI checks are green.
Recommended Action
- Fix the Important issue before merge.
- Address Suggestions opportunistically.
Failing closed on a disposition verb the parser doesn't know is correct, but the status then read "An unresolved finding from Ally's review of <sha> is still undispositioned" while Ally's ledger visibly dispositions that finding. Nothing distinguished vocabulary drift from a genuinely open finding, which is what made the missing `no-longer-applicable` verb expensive to diagnose. The carried-finding reason now names the verb when an unrecognized entry is what left the finding unretired. Written as a replacement message rather than a suffix: GitHub caps a commit-status description at 140 characters, the ordinary reason already spends 114, and appending would have pushed the explanation past the cap and lost precisely the detail this adds. The verb list carries its own budget for the same reason, since the ledger regex accepts an arbitrarily long verb. The diagnostic reuses the retirement matcher, so it can only ever name an entry that would otherwise have retired the finding — an unknown verb pointing at some other finding on the same head is not offered as the explanation. Ledger parsing collapses to one classified extractor rather than two filtered ones. `classifyPriorDisposition` makes the third state explicit: `retires`, `blocks`, `unrecognized`. Keeping `still-present` distinct from `unrecognized` is what stops a correctly-blocking finding from being reported as vocabulary drift.
Ally's review of 779ee7b caught this: when two later reviews disposition one finding differently — one `still-present`, one using a verb this parser does not know — the diagnostic reported the unknown verb. It named vocabulary drift as the cause of a block that Ally had deliberately left open with an explicit answer. A wrong explanation is worse than the bare reason this diagnostic replaces, so `blocks` now takes precedence: an unrecognized verb is only offered when no known blocking disposition names that finding.
779ee7b to
0239a61
Compare
|
Addressed in Two later reviews can disposition one finding differently — one
Suites: 89 across the seven review-gate files, On |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 0239a61
Prior Findings Dispositioned (1)
- prior:779ee7b important 1 — fixed —
server/src/services/pr-comment-review-gate.ts:271— matchingstill-presentledger entries now take precedence over unrecognized verbs, so the diagnostic cannot misattribute an explicitly blocking finding to vocabulary drift; the regression is covered atserver/src/__tests__/pr-comment-review-gate.test.ts:528.
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- The classified disposition extractor keeps retiring, blocking, and unrecognized verbs distinct while preserving fail-closed behavior.
- The diagnostic matches ledger entries to the specific reported finding and stays within the commit-status character cap.
- The added tests cover unrelated findings, long verbs, and precedence between explicit blocking and unknown dispositions.
Recommended Action
- Merge when the remaining required checks are green.
Thinking Path
Linked Issues or Issue Description
No existing issue — this is the follow-up flagged in #1568 (merged
bd35f20), described inline as a bug report.What happened. #1568 shipped a fail-closed allowlist of disposition verbs. During its own review Ally reported that
no-longer-applicablewas missing from that allowlist, so ledger entries using it were parsed and then dropped, leaving the finding blocking with nothing able to retire it.Expected. A reader looking at the red status can tell why the finding is still blocking.
Actual. The status read:
That sentence is true but actively misleading here: Ally's ledger does disposition the finding. Distinguishing "the verb is unknown to the parser" from "the finding is genuinely open" required reading the gate source. Ally described this as the finding being "silently ignored".
Not fixed by widening the allowlist. Adding
no-longer-applicable(which #1568 did) closes one instance. The diagnosability gap recurs on the next verb.What Changed
pr-comment-review-gate.ts: when an unrecognized ledger verb is what left a carried finding unretired, thecarried_findingreason names that verb.namesFinding), so it can only name an entry that would otherwise have retired that finding. An unknown verb pointing at a different finding on the same head is not offered as the explanation.ally-review-detection.ts: ledger parsing collapses to one classified extractor,extractAllyPriorFindingDispositions, replacing the two filtered ones.classifyPriorDispositionmakes the third state explicit —retires/blocks/unrecognized.still-presentis classifiedblocks, distinct fromunrecognized, so a correctly-blocking finding is never reported as vocabulary drift.No behavioral change to what the gate blocks on. Verdict and outcome are identical; only the human-readable reason differs.
Verification
pnpm exec vitest runinserver/:pr-comment-review-gate.test.ts— 30 passed (6 new)pnpm exec tsc --noEmit— cleanEvery new test was confirmed to discriminate by mutation. This mattered: my first pass at the "only blame the verb that named this finding" guard passed every test with the guard removed, so I added a case where the ledger names a finding the head never raised.
still-presentasunrecognizedMeasured message lengths against GitHub's cap (ordinary reason 114, unrecognized-verb reason 96, worst case with a 63-character verb 112).
Risks
Low. Parsing and message construction only — no schema, config, or deployment change, and no change to which PRs the gate blocks.
github-app-auth.tsandgithub-status-delivery-outbox.tsalready.slice(0, 140). This is written as a replacement message rather than a suffix for that reason — the ordinary reason already spends 114 — and the verb list carries its own budget because the ledger regex accepts an arbitrarily long verb. Pinned by a test.extractAllyDispositionedPriorFindingsandextractAllyUnrecognizedPriorDispositionsare removed in favour of the single classified extractor. Both were internal toserver/srcwith no direct test coverage; the gate was the only caller.Test-run note, reported because it is correlated with this change: the 8-file local combination above failed once on this branch with
Hook timed out in 60000msingithub-webhook.test.ts'safterAllDB cleanup, while master passed the same set. All 309 tests passed in that run — it is a teardown timeout, not an assertion. It did not reproduce on re-run (220s vs the 272s failing run), so it reads as a load-sensitive hook timeout on the shared embedded postgres that the six extra tests can tip over. I have deliberately not raisedhookTimeout, since that would mask pre-existing fragility in a file this PR does not otherwise touch. Flagging it rather than filtering it out.Model Used
Anthropic
claude-opus-5(1M context), via Claude Code.