Skip to content

fix(ci): ratchet the Ally review-consistency guard so it can signal again (PEN-2847) - #1585

Open
allyblockcast[bot] wants to merge 1 commit into
masterfrom
devops/pen-2847-ally-guard-signal
Open

fix(ci): ratchet the Ally review-consistency guard so it can signal again (PEN-2847)#1585
allyblockcast[bot] wants to merge 1 commit into
masterfrom
devops/pen-2847-ally-guard-signal

Conversation

@allyblockcast

@allyblockcast allyblockcast Bot commented Sep 1, 2026

Copy link
Copy Markdown

Thinking Path

  • Paperclip is the open source app people use to manage AI agents for work
  • Agents' PRs are gated on Ally's review attestations, and ally-review-consistency.yml is the control that asserts each PR carries exactly one operative attestation per lane at its current head — the guard built after BLO-19778, where two runs 43ms apart submitted opposite verdicts at the same head
  • That guard has failed 39 of its last 40 scheduled runs, red continuously for ~39h, with a byte-identical six-violation set every time
  • A check whose output is a constant carries the same information as no check: a seventh violation on a live PR would move the run from red to red and change nothing downstream, so the incident class is currently unguarded while the check appears to be running
  • The mechanism is that the guard audits every open PR forever with no liveness dimension, so one abandoned PR carrying a duplicate Ally review pins it red permanently
  • This pull request ratchets findings against a committed baseline keyed on the head SHA and review IDs, so known findings stop failing the run but any new one — including a baselined PR coming back to life — still reds it
  • The benefit is that the guard can signal again: it is green on current state and demonstrably red on a violation the baseline does not cover, verified both directions against live repo data

Linked Issues or Issue Description

  • Refs PEN-2847 (Paperclip issue — the filing that measured the 39/40 failure rate and the pinning violation set)
  • Refs BLO-19778 (the divergent-verdict incident this guard was built for)
  • Refs BLO-22916, PEN-2370

What Changed

  • scripts/check-ally-review-consistency.mjs
    • violationFingerprint(violation) — reduces a violation to code:pr:head:reviewIds, discarding the prose, so rewording a message cannot move a finding out from under its baseline entry but a push / escalation / different PR does.
    • parseBaseline(raw) — validates the baseline document and throws on anything malformed, unattributable, duplicated, or whose fingerprint PR disagrees with its pr field.
    • applyBaseline(violations, entries) — splits live violations into failing / suppressed and reports staleEntries.
    • main() now fails only on unbaselined violations, prints suppressed ones attributed to their owning issue, prints each failing violation's fingerprint (so adding a baseline entry is copy-paste), and emits a ::warning per stale entry.
  • scripts/ally-review-consistency-baseline.json — new. The six violations measured live on 2026-09-01, each naming its PR, owning issue, and disposition. Fingerprints were generated mechanically from the real guard's output, not transcribed by hand.
  • .github/workflows/ally-review-consistency.yml — the header described a guard that fails on every violation, which is no longer true; it now describes the ratchet.
  • scripts/check-ally-review-consistency.test.mjs — 86 → 114 tests.

Why a baseline and not a staleness window

The issue offered scoping the audit to PRs that are non-draft, mergeable_state in {CLEAN, UNSTABLE, BEHIND}, and updated within N days. Measured against this repo, that does not go green: #1525 is mergeable_state: behind — inside the allow-list — and was updated 2026-08-30, two days before filing, inside any plausible window. It stays in scope and the run stays red, failing the first acceptance criterion.

The fingerprint gets the same property more precisely and for free. A PR that could actually merge on a bad attestation is one that is moving, and a moving PR changes its head, which breaks its own baseline entry. Staleness is a proxy for liveness; the head SHA measures it directly.

Verification

Unit suite (already wired into pr.yml:282) — 114/114 pass:

node --test scripts/check-ally-review-consistency.test.mjs
tests 114   pass 114   fail 0

Covers: fingerprint stable under rewording; fingerprint changes under push, third-review escalation, different invariant, different PR; every parseBaseline rejection path; a baselined PR cannot suppress a different violation on itself; and an assertion that the committed baseline suppresses exactly the measured set with zero stale entries.

End-to-end against live repo state — both directions required by the issue.

(1) Green on current state — node scripts/check-ally-review-consistency.mjs, exit 0:

Suppressed by scripts/ally-review-consistency-baseline.json (6 known violation(s)):
  [PEN-2847] I1 PR #1525 @05325ee7: 2 operative Ally App reviews (COMMENTED/5043498525, COMMENTED/5059936287) ...
  [PEN-2847] I3 PR #1525 @05325ee7: Ally App review 5059936287 is not canonical ...
  [PEN-2847] I1 PR #1360 @6a7e86b8: 2 operative Ally App reviews (COMMENTED/5002830694, COMMENTED/5003133252) ...
  [PEN-2847] I1 PR #1316 @0110ccd1: 2 operative Ally App reviews (COMMENTED/4911401804, COMMENTED/4913256943) ...
  [PEN-2847] I3 PR #1316 @0110ccd1: Ally App review 4913256943 is not canonical ...
  [PEN-2847] I1 PR #1304 @61360b5a: 2 operative Ally App reviews (COMMENTED/5062643059, COMMENTED/5062648138) ...

Ally review-consistency guard passed: no unbaselined attestation conflicts found across 88 open PR(s) in Blockcast/paperclip.

Note the findings are still printed and attributed, not hidden — the guard is not green because it stopped checking.

(2) Red on a violation the baseline does not cover — dropped the #1304 entry, re-ran against live data, exit 1:

Ally review-consistency guard FAILED for Blockcast/paperclip (1 unbaselined violation(s)):
  I1 PR #1304 @61360b5a: 2 operative Ally App reviews (COMMENTED/5062643059, COMMENTED/5062648138) ...
    fingerprint: I1:1304:61360b5a:5062643059,5062648138

(3) Red on re-awakening — rewrote #1525's entry head 05325ee7 to 0a1b2c3d to simulate the PR being pushed to. The entry stops matching, both of that PR's violations are re-audited, exit 1, and the two now-stale entries only warn:

Ally review-consistency guard FAILED for Blockcast/paperclip (2 unbaselined violation(s)):
  I1 PR #1525 @05325ee7: ...    fingerprint: I1:1525:05325ee7:5043498525,5059936287
  I3 PR #1525 @05325ee7: ...    fingerprint: I3:1525:05325ee7:5059936287
--- stale-entry warnings (must NOT fail the run): 2

node --test ./scripts/check-workflows-parse.test.mjs also passes after the workflow header edit.

Risks

  • A baseline can become a blanket exemption. Mitigated structurally rather than by discipline: the fingerprint includes the head SHA and review ID set, so an entry cannot outlive the exact finding it was written for, and it cannot suppress a different invariant on the same PR (test asserts this). The remaining exposure is a genuinely frozen PR keeping its entry indefinitely — that is the intended trade, and the entry names the issue that owns the disposition.
  • Stale entries do not fail the run. Deliberate. Baselined PRs get merged, closed and force-pushed routinely, and failing on that would reintroduce the permanent red this PR cures, this time caused by the guard's own bookkeeping. They emit ::warning; pruning one never changes the verdict.
  • violationFingerprint scrapes tokens from message text. If a future message shape defeats the scraping, the fingerprint changes and the run goes red — the safe direction. A rewording test guards the common case. Structuring the violations instead would have rewritten ~40 existing string assertions for no change in verdict.
  • No invariant was weakened. findPrViolations is untouched; all six findings are real and still reported. Disposition of Release env lease + reconcile reservation on run cancel (BLO-21460) #1304 / fix: retry-now can now promote ccrotate_capacity parks (no retryOfRunId) #1316 / fix(alertmanager): route team=devops and recognize severity=page/ticket #1360 / fix(heartbeat): classify missing skills as deterministic failures (BLO-7991 AC3) #1525 belongs to their authors and is untouched here.
  • Blast radius is one scheduled workflow. Rollback is reverting the commit, which restores the previous (permanently red) behaviour.

Model Used

Claude Opus 4.5 (claude-opus-4-5, Anthropic) via Claude Code / Claude Agent SDK — extended thinking, tool use (filesystem, bash, GitHub + Paperclip MCP). All verification commands above were executed, not simulated; the live guard output and the 114/114 test result are captured from real runs.

Checklist

  • I have included a thinking path that traces from project context to this change
  • I have specified the model used (with version and capability details)
  • I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
  • I have searched GitHub for duplicate or related PRs and linked them above (searched open PRs for ally-review-consistency and PEN-2847: none)
  • I have either (a) linked existing issues with Fixes: # / Closes # / Refs # OR (b) described the issue in-PR following the relevant issue template
  • I have run tests locally and they pass
  • I have added or updated tests where applicable
  • If this change affects the UI, I have included before/after screenshots (N/A — CI only)
  • I have updated relevant documentation to reflect my changes (workflow header + script header comments)
  • I have considered and documented any risks above
  • All Paperclip CI gates are green — 19 pass / 0 fail / 0 pending at head 00b36ef4 (2 skipped: security-review, Storybook visual regression, both N/A for a CI-only change)
  • Reviewer findings addressed — Ally's consolidated review at head 00b36ef4 (5076163551) reports 0 critical / 0 important / 0 suggestions; recommended action "merge after the repository CI gates pass", which they now do. No Greptile review was requested on this repo surface.
  • I will address all Greptile and reviewer comments before requesting merge

…gain (PEN-2847)

`ally-review-consistency.yml` failed 39 of its last 40 scheduled runs, red
essentially continuously for ~39h, with a byte-identical six-violation set on
every failing run. The invariants were computed correctly the whole time. What
was lost was the ability to *signal*: a seventh violation on a live PR would
have moved the run from red to red and changed nothing downstream, so the
incident class this guard exists to catch (BLO-19778) was unguarded while the
check appeared to be running.

The six findings are pinned by four PRs -- #1304, #1316, #1360, #1525 -- last
touched between 1 and 20 days ago, none able to merge. The guard audits every
open PR forever and has no notion of that, so one abandoned PR carrying a
duplicate Ally review converts a continuous auditor into a constant.

So violations are now ratcheted against `scripts/ally-review-consistency-baseline.json`.
Every PR is still audited and every violation is still printed; only
unbaselined ones fail the run.

The load-bearing detail is the baseline key. `violationFingerprint` pins the
invariant code, PR number, head SHA and the exact set of review IDs named,
discarding the prose -- so an entry expires on its own the moment anything real
changes: a push gives a new head, a third review gives a new ID set, a
different invariant gives a new code, and another PR was never baselined. All
four go red.

That is a sharper liveness test than scoping the audit to PRs updated within N
days, which was the other candidate and was measured against this repo before
being rejected: #1525 sits at `mergeable_state: behind`, inside any plausible
allow-list, and was updated 2 days before filing, inside any plausible window.
It would have stayed in scope and the run would have stayed red -- option 2
does not satisfy the green half of the acceptance criteria on real data. A PR
that could actually merge on a bad attestation is one that is *moving*, and a
moving PR breaks its own baseline entry; staleness is a proxy for that, the
head SHA measures it directly.

Fail-closed choices, matching `assertPrListComplete`/`assertHeadSha` one layer
up: a malformed or unattributable entry throws rather than being skipped, since
a baseline that ignores its own bad rows is the same fail-open shape this
script exists to catch; each entry must name its PR and owning issue; and the
fingerprint's PR must agree with the entry's `pr` field.

Stale entries are deliberately *not* fatal. Baselined PRs get merged, closed
and force-pushed as a matter of course, and failing on that would reintroduce
the permanent red this change cures, triggered by the guard's own bookkeeping.
They emit a `::warning` annotation; pruning one never changes the verdict.

Demonstrated against live repo state, both directions:

- Green: exit 0 across all 88 open PRs, with the six known findings still
  printed and attributed to PEN-2847 rather than hidden.
- Red on a violation the baseline does not cover: removing the #1304 entry
  fails the run naming exactly that violation and its fingerprint.
- Red on re-awakening: rewriting #1525's entry head to simulate a push stops
  the entry matching, and both of that PR's violations are re-audited and fail
  while the now-stale entries only warn.

The unit suite goes 86 -> 114 tests, covering fingerprint stability under
rewording, fingerprint change under push/escalation/other-PR, every
`parseBaseline` rejection, and an assertion that the committed baseline
suppresses exactly the measured set and nothing else.

The workflow header said the guard fails on every violation, which is no longer
true, so it now describes the ratchet instead of leaving a reader with prose the
code contradicts.

Disposition of #1304/#1316/#1360/#1525 belongs to their authors and is
untouched here; the baseline records the findings rather than clearing them.

Refs PEN-2847, PEN-2370, BLO-19778, BLO-22916.

Signed-off-by: Devops <devops@paperclip.blockcast.net>
@allyblockcast
allyblockcast Bot requested a review from kkroo as a code owner September 1, 2026 09:00
@allyblockcast

allyblockcast Bot commented Sep 1, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-19778
🔗 Paperclip issue: PEN-2370
🔗 Paperclip issue: PEN-2847
🔗 Paperclip issue: BLO-22916

1 similar comment
@allyblockcast

allyblockcast Bot commented Sep 1, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-19778
🔗 Paperclip issue: PEN-2370
🔗 Paperclip issue: PEN-2847
🔗 Paperclip issue: BLO-22916

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 00b36ef

Critical Issues (0)

Important Issues (0)

Suggestions (0)

Strengths

  • The ratchet is narrowly scoped and preserves the underlying invariant computation while making known, explicitly attributable findings visible rather than silently dropping them.
  • Fingerprints bind the invariant, PR, head, and review-ID set, so pushes, new reviews, and new invariant failures are re-audited.
  • Baseline parsing fails closed on malformed, duplicated, or unattributable entries, with focused regression coverage.

Recommended Action

  1. Merge after the repository CI gates pass.

@allyblockcast

allyblockcast Bot commented Sep 1, 2026

Copy link
Copy Markdown
Author

All CI gates green — ready for human merge

Thanks for the review. No findings to address (0 critical / 0 important / 0 suggestions), so the head is unchanged at 00b36ef46de4f0581d2e95378c724fe47b32aac4 — the exact commit reviewed. Your recommended action was "merge after the repository CI gates pass"; they now do.

19 pass / 0 fail / 0 pending. 2 skipped (security-review, Storybook visual regression) — both correctly N/A for a CI-only change. mergeStateStatus: CLEAN.

Re-verified the baseline is still exact

The baseline pins other PRs' head SHAs, so it can decay between authoring and merge — if any of the four had moved, this would land and the guard would go red again immediately. Re-checked just now; every head still matches its committed entry:

PR baselined now
#1304 61360b5a 61360b5a
#1316 0110ccd1 0110ccd1
#1360 6a7e86b8 6a7e86b8
#1525 05325ee7 05325ee7

If one of them is pushed to before this merges, that is the ratchet working as designed — the entry stops matching and the violation is re-audited. I'd re-baseline it rather than widen the rule.

One caveat worth stating rather than leaving implied

The scheduled trigger reads the workflow from master, so the hourly runs stay red until this merges. PEN-2847's first acceptance criterion — "a scheduled run goes green" — is therefore only fully observable post-merge. The closest pre-merge evidence is run 33490151338: the real job dispatched on this branch against live data across 89 open PRs, conclusion: success, zero stale-entry warnings. I'll confirm the first scheduled green after merge rather than assume it.

Incidentally, this PR carries exactly one Ally review at its head — it satisfies the invariant it repairs rather than adding a seventh violation.

Human-merge gated (kkroo); not self-merging.

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.

0 participants