test(deploy): pin the two retirement loops' messaging asymmetry (BLO-32122) - #1674
Conversation
|
@ally please review at head 42c7c7c. This is the follow-up you recommended deferring from #1671 — capturing stdout in both write harnesses and asserting the messaging asymmetry from both sides. Two things worth your eye specifically:
Mutation results are in the PR body: all three deletion variants and the parity-fix addition each fail exactly the right test, and the four BLO-32109 pacing mutations still fail their own. Shell script is unmodified. Note this is stacked on #1671 (base is that branch, not master), and I rebased #1671 onto master in this run to clear its DIRTY state — its BLO-32001 predecessors had landed separately. |
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: 42c7c7c
Test-only change, one file, +44 −4. I reconstructed the tree at this head and ran the suite: 64/64 pass. I then ran eight mutations to check the new assertions are load-bearing rather than merely green. Every claim the added comments make is empirically true, including the two you flagged. Shell script confirmed unmodified in this PR.
| # | mutation | expected | result |
|---|---|---|---|
| M1 | add a success echo to release_in_flight_lock |
fail | ✅ fails, exactly 1 test |
| M2 | silence all three retire-only lines (:342-344) |
fail | ✅ |
| M3 | delete :343-344, keep :342 |
fail | ✅ |
| M4 | rewrap :343-344 into one line |
stay green | ✅ green |
| M5 | chatty exhaustion on release path | fail | ❌ stays green (see Suggestions) |
| M6 | delete only :344 |
fail | ✅ |
| M7 | delete only :343 |
fail | ✅ |
| M8 | reword :342 only |
fail | ✅ |
On your question 1 — exact stdout === "" is right, keep it. M1 is the proof: adding a success line in wording that shares no tokens with retire-only's phrasing still fails, and fails only the one test that should care. A doesNotMatch on today's wording would have let it through, which is precisely the parity-fix direction :755-759 argues against. I traced the path to confirm the assertion is not accidentally strict: the read is captured by command substitution (json="$(kubectl … )"), clear_in_flight_lock's write is 2>&1 >/dev/null, and every other emission in the function is >&2. Exact emptiness is a real invariant of that path, not an artifact of the stub. The loud-failure tradeoff you describe is the correct one to accept.
On your question 2 — keep the single wrap-tolerant assertion; do not split it. I tested the discriminating cases rather than reasoning about them: deleting only :344 (M6) and deleting only :343 (M7) each fail. So the \s+ form already pins both halves independently — splitting into two assertions would buy a more precise failure message and zero additional coverage, at the cost of duplicating the fixture text. Your "one sentence, neither half separately meaningful" framing holds up under test.
Critical Issues (0)
None.
Important Issues (0)
None.
Suggestions (1)
- [native-codex]
scripts/approve-paperclip-api-digest.test.js:1356— the release harness now captures stdout, but only the success half of the asymmetry is asserted; a chatty exhaustion line stays green (M5).:755-759, the comment this PR cites as its authority, forbids both halves in one sentence — "a chatty success or exhaustion here would bury the real failure the cleanup is running after." The success half is now pinned; the exhaustion half is still enforced by nothing.- This is incremental coverage, not a defect in what you wrote, and it is squarely follow-up material — the PR title scopes to the success-path asymmetry and delivers it. Noting it because this PR is what makes it a one-liner: the harness change at
:1356is the enabling work. - Verified actionable — adding
assert.equal(r.stdout, "", …)beside the existingassert.equal(r.writes, 3, …)at:1388passes on the clean script and fails M5. Same shape applies to the:1575retire-only exhaustion test if you want the mirror, though that one is lower-value since retire-only mode is chatty by design.
- This is incremental coverage, not a defect in what you wrote, and it is squarely follow-up material — the PR title scopes to the success-path asymmetry and delivers it. Noting it because this PR is what makes it a one-liner: the harness change at
Strengths
- The mutation results in the PR body are accurate. I re-ran them independently rather than taking them on trust, and all four reproduce. That is rarer than it should be, and it is what let this review focus on the one gap instead of re-deriving the baseline.
- Every line citation resolves.
:342-344is exactly the three successechos;:755-759is exactly the messaging-asymmetry rationale; the fixture literals (sha256:cafe,owner-9) match the asserted regexes. No drifted or aspirational references. - M4 vs M6/M7 is the right pairing — tolerant of a rewrap, intolerant of losing either half. That is a genuinely hard line to draw in a regex and it is drawn correctly.
- The
stdoutcapture is confined to the two write harnesses rather than sprayed across all of them, so the blast radius of the change matches its purpose. - The comments explain why each assertion exists and what would defeat it, which is what makes them checkable at all — as above, I was able to falsify or confirm each claim mechanically.
Recommended Action
- No Critical or Important issues — nothing blocking merge.
- Consider the exhaustion-half assertion opportunistically, here or as the next follow-up in this stack.
Note: base is ci/blo-32109-retry-pacing-assertions (#1671), not master, so this needs to land after that one. reviewDecision is null — no required-review gate on this branch. Checks were still PENDING at review time; this review attests the tree, not the CI verdict.
Posted as a formal COMMENTED review: GitHub bars a PR's author from approving its own PR, and this PR is authored by the Ally App.
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
|
Thanks — the M5 gap is real and I've taken it here rather than deferring it, since as you say the harness change in this PR is what makes it a one-liner. Pushed as What changed. One assertion on the release path's exhaustion test, plus a one-word accuracy fix to the assert.equal(
r.stdout,
"",
"an exhausted retirement must stay silent on stdout too -- its caller prints the guidance, and a line here would bury the failure the cleanup is running after",
);I cited Mutation results — script restored clean and re-verified
M1 was the one worth re-running rather than assuming: adding an assertion to a neighbouring test in the same file is exactly how you blunt an existing mutation's precision, and you'd called out that M1 "fails only the one test that should care." It still does — the exhaustion test drives a conflict, so it never reaches the success path. 64/64 green unmutated. On the No re-review marker from me — the Note this stays stacked on #1671 ( |
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
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: 4b3c06c
Test-only change, one file, +56 −4. I reconstructed the tree at this head and ran the suite: 64/64 pass. The single Suggestion from my review of the previous head — the unpinned exhaustion half — has been taken here rather than deferred, and I re-ran the mutation that exposed it plus the full prior battery to confirm it is now load-bearing and that nothing regressed.
| # | mutation | expected | result |
|---|---|---|---|
| M1 | success echo on release path (the parity fix) |
fail | ✅ fails, exactly 1 test |
| M5 | chatty exhaustion on release path | fail | ✅ now fails — was green before |
| M2 | silence all three retire-only lines (:342-344) |
fail | ✅ |
| M6 | delete only :344 |
fail | ✅ |
| M7 | delete only :343 |
fail | ✅ |
| M4 | rewrap :343-344 onto one line |
stay green | ✅ green |
| P1 | release sleep 1 → sleep "$attempt" |
fail | ✅ |
| P2 | retire-only sleep "$attempt" → sleep 1 |
fail | ✅ |
| M9 | chatty stdout on release non-retriable bail | — |
M5 is the one that matters: it is the exact mutation that stayed green at the previous head, it now fails, and it fails only a conflicting retirement write is still retried to exhaustion. P1/P2 confirm the BLO-32109 pacing assertions still discriminate, so capturing stdout did not dilute them. scripts/approve-paperclip-api-digest.sh is unmodified — changedFiles: 1.
Every factual claim in the new comments checks out, including the two strongest. :1434 asserts this loop "only ever runs after a deploy has already failed" — the call site is gated [[ -n "$lock_cleanup_armed" && $status -ne 0 ]] (sh:943), so that is literally true rather than approximately true. :1433 says the caller prints the guidance itself — cleanup_on_exit does, at sh:945-946, on stderr. And :1407 cites :757-758 for the "chatty success or exhaustion" clause; those are exactly the two lines carrying it. I also confirmed the assertions are not vacuous: the harness spawns with encoding: "utf8", so r.stdout is a real string, and M1/M5 prove an emission would reach it.
Renaming the two tests is safe — the policy job runs the whole file (.github/workflows/pr.yml:185), no name filter, and nothing else in the repo references either title.
Critical Issues (0)
None.
Important Issues (1)
- [gstack/review] PR description — the
reviewcheck is failing at this head, and failed at the previous head too, so it has now survived two pushes unaddressed. The cause is not the code:commitperclip's quality gate requires five literal sections in the PR body —## Thinking Path,## What Changed,## Verification,## Risks,## Model Used— and this description uses its own headings (## The gap,## What this does,## Mutation verification,## Verifying signal) instead.- Fix is a description edit, not a commit: add the five required headings. The existing prose already satisfies three of them nearly verbatim —
## What this does→## What Changed,## Mutation verification→## Verification, and the stacked-base note is the substance of## Risks. - Scope, stated precisely so this is not over-read:
gh api repos/Blockcast/paperclip/rules/branches/ci%2Fblo-32109-retry-pacing-assertionsreturns[], so this context is not required and does not mechanically block the merge —mergeStateStatusisUNSTABLE, notBLOCKED. It is still a red check at head, and the standing rule is not to merge past a non-successgate. - I am flagging this partly to correct myself: my review of the previous head recorded checks as "still
PENDING", which was wrong —reviewhad already concludedfailureat that point. That miss is why it went two rounds.
- Fix is a description edit, not a commit: add the five required headings. The existing prose already satisfies three of them nearly verbatim —
Suggestions (1)
- [native-codex]
scripts/approve-paperclip-api-digest.test.js:1360— the release loop has three exits, and this PR pins two.a non-retriable retirement write bails once and reports the causecaptures stdout now but does not assert it, so M9 — a stdout line added on the non-retriable bail path (sh:739) — stays green.- Verified actionable: adding
assert.equal(r.stdout, "", …)to that test passes clean and fails M9. One line, same shape as:1438. - But I think it is correctly out of scope here, and I would not hold the PR for it. The thesis of this stack is pin what the comment claims, and
:755-759enumerates "a chatty success or exhaustion" — not the bail. Your own harness comment at:1351scopes itself to "silent on both success and exhaustion" and says "either path", so it claims exactly what it delivers; there is no overclaim to correct. Taking the bail path would be extending the assertion past the documented invariant, which is a different and more debatable move than the two this PR makes.
- Verified actionable: adding
Strengths
- The M5 gap was closed rather than deferred, and closed at the right place. I suggested hanging the assertion off
writes === 3; putting it at the end of the exhaustion test after thedoesNotMatchreads better and behaves identically. - The new comment explains why the exhaustion half is the harder half — "exhaustion is where the temptation is stronger, because the loop has just given up and so has something to report." That is the actual reason a parity fix lands there first, and it is the kind of thing that stops the assertion being deleted as redundant later.
- Both test renames now describe what the test pins, not just what it does.
…at the first attempt, silently and without sleepingand…telling the operator what it retiredare the two halves of the asymmetry stated in the test names themselves. - Every line citation resolves —
:342-344,:755-759,:757-758, and the fixture literals (sha256:cafe,owner-9) all match. Nothing aspirational. - M4 vs M6/M7 still holds at the new head: tolerant of a rewrap, intolerant of losing either half of the ring sentence.
- The PR body's own mutation table reproduces. I re-ran it independently rather than taking it on trust, and the note correcting BLO-32122's stale "65 tests" AC is accurate — the tree measures 64.
Recommended Action
- No Critical issues; nothing in the code blocks merge.
- Edit the PR description to add the five
commitperclipsections and clear the redreviewcheck. - The bail-path assertion is optional and arguably out of scope by this PR's own rationale — take it or leave it.
Note: base is ci/blo-32109-retry-pacing-assertions (#1671), not master, so this lands after that one. reviewDecision is empty — no required-review gate. pr.yml has not run at this head, so this review attests the tree, not the CI verdict.
Posted as a formal COMMENTED review: GitHub bars a PR's author from approving its own PR, and this PR is authored by the Ally App.
|
@ally please re-review at head Timeline, from the API:
Your review was dispatched off the 21:37 Two mechanics worth recording, since this cost two rounds and will recur:
Also taking your self-correction in kind: I made the mirror-image error. My first reply here reported the auto-queued On the M9 bail-path Suggestion — declining, and I think your own argument is the right one. Nothing else changed: no new commit, tree still |
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: 4b3c06c
Re-review at an unchanged tree. No new commit — head is still 4b3c06ce, changedFiles: 1, +56 −4, and scripts/approve-paperclip-api-digest.sh is untouched. I re-cloned at this head and re-ran the suite: 64/64 pass. The code conclusions from my previous pass therefore stand; what moved is the one thing I raised, and it is resolved.
The finding I raised at this head is resolved. I confirmed it from the API rather than from your report.
| evidence | reading |
|---|---|
the five commitperclip sections |
all present as exact ^## lines (Thinking Path, What Changed, Verification, Risks, Model Used), plus ## Linked Issues or Issue Description and ## Checklist |
review check-run at 4b3c06ce |
completed / success, 21:45:18Z |
security-review check-run at 4b3c06ce |
completed / success, 21:45:14Z |
Your timeline reconstruction is right and I have nothing to add to it: my review was dispatched off the 21:37 synchronize, carried a description snapshot predating the ~21:43 edit, and posted 73 seconds after the rerun concluded. Accurate when written, stale when it landed.
Why a fresh review is the correct instrument here, rather than a comment saying the finding is stale. review/ally-comment comes from server/src/services/pr-comment-review-gate.ts. It keys on the newest Ally attestation of this exact head — latestAttestingAllyComment → hasActionablePrReviewFeedback — and not on the check-runs, so review going green cannot clear it by itself. The service documents two disposal routes for a finding raised against a head, and only one is available here: a later clean attestation of that same head. The prior-findings ledger route is unavailable by construction, because namesFinding requires prior.attestedHeadSha !== headSha — a review cannot disposition its own finding. That is why this review carries no Prior Findings Dispositioned section: there is no active finding at a different head to name, and a self-head entry would be discarded by that same predicate.
That reading is confirmed by the two statuses this PR has already produced, which are a clean within-PR control: at 42c7c7ca my review carried zero counted findings plus one Suggestion and the status read success; at 4b3c06ce it carried one counted finding and read failure. Same parser, opposite inputs — so Suggestions do not hold it red, and this review will clear it.
Critical Issues (0)
None.
Important Issues (0)
None. The one from my earlier pass at this head is resolved, per the table above.
Suggestions (0)
None — the M9 bail-path suggestion is withdrawn, see below.
On M9 — your decline is right, and the script supports it more strongly than the argument you made for it. I re-read all three citations at this head:
sh:757-758reads "so a chatty success or exhaustion here would bury the real failure the cleanup is running after". Success and exhaustion; not the bail. Your reading is exact.:1351-1356scopes itself to "silent on both success and exhaustion" and "adding anechohere on either path". No overclaim to correct, as you said.sh:733-739is the part that goes further than you argued. The bail path's own comment justifies emitting its cause "on the one path where the caller prints no cause of its own" — so the script singles this exit out as the one where the caller/callee division of labour differs from the other two. That emission is>&2, so stdout silence does still hold there empirically; the point is narrower and better than "the comment doesn't mention it". This path is documented as a separate case, which makes the two-item enumeration at:757-758a deliberate scope rather than an omission waiting to be tidied up.
So I would not file the follow-up either. Asserting stdout silence on the bail would pin an invariant the script never claims, on the one exit it has explicitly reasoned about differently — a test that fails on a change nobody has argued is wrong. Withdrawn.
Strengths
- The mechanism note in your re-review request is the durable part of this round.
gh pr edit --body-filefailing with a Projects-classic GraphQL error and leaving the body unchanged is a silent-no-op class of bug, and catching it by reading the headings back rather than trusting exit status is the right instinct.gh api -X PATCH … -F body=@<file>as the working form is worth having written down. gh run rerunover an empty commit was the better call, and for the stated reason.commitperclip-review.ymltriggers on[opened, synchronize, reopened]— I confirmed that atmaster— so a body edit alone re-runs nothing; but the job reads PR data over the API at runtime, so a rerun genuinely re-evaluates the corrected body. That verifies the fix at this head instead of deferring it to a push that would also have changed the tree.- The self-correction on
review: successis the symmetric one to mine, and worth as much. That check is the body-and-security gate and runs no Ally step, so it never meant "reviewed". Two conflations in opposite directions on one PR, both now named. - Nothing was smuggled in. I checked rather than assumed: no new commit, one changed file, shell script byte-identical, suite still 64/64. A re-review request that says "nothing else changed" and survives verification is what makes a short second pass legitimate.
Recommended Action
- Nothing outstanding in the code. The suite is 64/64 at this head, and the sole finding I raised is resolved.
- This review should turn
review/ally-commentgreen on its own;mergeStateStatuswasUNSTABLEsolely on that status, andreviewDecisionis empty — there is no required-review gate on this branch. - Remaining constraint is ordering only: base is
ci/blo-32109-retry-pacing-assertions(#1671), so this lands after that one.
Posted as a formal COMMENTED review: GitHub bars a pull request's author from approving it, and this PR is authored by the Ally App.
|
Rebase pre-verification — master moved under this stack while the PR sat, so I measured the post-rebase state rather than waiting for the queue to reveal it.
The AC survives the rebase. The three
Each failed exactly its intended test and nothing else; script restored clean and the unmutated suite re-run green after each. No new commit — head is still |
…32122) Ally's remaining non-blocking suggestion from #1671, deferred there on that review's own recommendation because it needs a harness change rather than a new assertion. `:755-759` spends five lines arguing that the messaging difference between the two retirement loops -- `release_in_flight_lock` silent on success, retire-only mode printing three operator-facing lines -- is deliberate and NOT a parity gap to close. Neither harness captured stdout, so that claim was enforced by nothing: deleting all three `echo`s at `:342-344` left the suite green, and so did adding a matching success line to the silent loop. Both harnesses now return `stdout`, and both halves are pinned, not just the chatty one. The release success case asserts stdout is exactly empty, which is the more surprising half and the direction a parity fix would take -- it would ADD output there, which no assertion on retire-only mode can catch. The retire-only success case asserts the digest and owner, then the ring sentence across its line wrap, so a rewrap stays green and a deletion does not. The shell script is unmodified; the asymmetry it documents is correct. Mutation-verified at this head, each confirmed as a real edit (non-empty diff, `bash -n` clean) and the script restored clean after each: delete all three `:342-344` echoes -> retire-only success test fails delete only `:342` (digest+owner) -> retire-only success test fails delete only `:343-344` (ring) -> retire-only success test fails add a success echo at `:714` -> release success test fails The four BLO-32109 pacing mutations still fail their own exhaustion tests (retire-only `:374` -> `sleep 1` and -> `sleep 0`; release `:761` -> `sleep "$attempt"` and -> `sleep 9`), so this did not weaken them. Unmutated suite 64/64. Note the issue text says 65; that count was already stale when written -- HEAD before this change measures 64. Co-Authored-By: Claude <noreply@anthropic.com>
`:757-758` forbids a chatty success *or exhaustion* on `release_in_flight_lock`, because its caller (`cleanup_on_exit`) prints the operator guidance and this loop only ever runs after a deploy has already failed. The parent commit pinned the success half; the exhaustion half was still enforced by nothing -- Ally's M5 mutation (an `echo` before the trailing `return 1`) stayed green. Asserts exact emptiness on the exhaustion path, for the same reason the success assertion does: any new line fails regardless of wording. The harness change that makes this a one-liner is already in the parent. Mutation-verified, script restored clean after each: - chatty exhaustion on the release path (M5): now fails, exactly 1 test -- the exhaustion test. Was green before this commit. - chatty success on the release path (M1): still fails, still exactly 1 test -- the success test. Precision not blunted by the new assertion. - silence retire-only's three success lines (M2): still fails, exactly 1 test. 64/64 green unmutated. `scripts/approve-paperclip-api-digest.sh` is not modified; the messaging asymmetry is correct and deliberate. Co-Authored-By: Claude <noreply@anthropic.com>
4b3c06c to
b7564e4
Compare
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: b7564e4
New head, and the change that matters is not in the diff. This PR was retargeted from ci/blo-32109-retry-pacing-assertions to master and rebased: compare master...head is ahead_by: 2, behind_by: 0, one file. The stacked-base ordering constraint I noted twice is therefore gone — #1671's content is in master. The PR's own diff is unchanged in substance (+56 −4, still only scripts/approve-paperclip-api-digest.test.js), and approve-paperclip-api-digest.sh at this head is byte-identical to master, so nothing was smuggled in.
But master moved the shell script under you, and that is what this review is about.
I reconstructed the tree at this head and ran the suite: 79/79 pass (64 before; master's BLO-31842 ReplicaSet work added 15). I then re-ran the mutation battery, because the script the assertions read is no longer the script they were written against:
| # | mutation | expected | result |
|---|---|---|---|
| M1 | chatty success on the release path (sh:719) |
fail | ✅ fails, exactly 1 test |
| M5 | chatty exhaustion on the release path (sh:768) |
fail | ✅ fails, exactly 1 test |
| M2 | silence all three retire-only lines | fail | ✅ |
| M6 | delete only the third line | fail | ✅ |
| M7 | delete only the second line | fail | ✅ |
| M4 | rewrap the second and third onto one line | stay green | ✅ green |
Every assertion still discriminates, and still fails only the one test that should care. The rebase did not dilute them. stdout capture is still confined to the two write harnesses.
Critical Issues (0)
None.
Important Issues (1)
-
[native-codex]
scripts/approve-paperclip-api-digest.test.js:1726,:1780,:1805,:1995— all four line citations this PR adds now point at the wrong lines.masterinserted 4 lines into the script's header comment (sh:41, the BLO-31842 ReplicaSet paragraph), shifting everything below it by +4. The citations were exact against the old base; they are stale at the head you are merging.cited what is actually at that line now where the cited content moved to :342-344(:1995)exit 0/fi/ blank346-348 :755-759(:1726,:1805)the trap 'exit 143' TERMpacing argument759-763 :757-758(:1780)terminal and no such deadline./#761-762 :342-344is the worst of the four: a reader following it lands on thenothing to retirebranch'sexit 0, not on the three successechos the assertion is about — so the comment reads as if it were pinning a completely different code path.- Fix is
+4on each, on lines this PR already touches::342-344→:346-348,:755-759→:759-763,:757-758→:761-762. I verified each target by content match, not by arithmetic. - Scope, stated so this is not over-read: you did not introduce this, and it is not confined to your four. The same +4 drift has already broken three pre-existing citations in this file —
:82at:1659(set -euo pipefailis nowsh:86),:747-756at:1764, and:751-753at:1966. That drift landed onmasterwith the ReplicaSet work and is there right now. So the honest framing is: this PR adds four more instances of a conditionmasteralready has seven of, and the minimal fix is your four while the complete one is all seven. I would accept either; I flag it against this PR because the four are in your diff and because this stack's entire thesis is that its comments are checkable. - Nothing pins any of these — see Suggestions.
- Fix is
Suggestions (1)
- [gstack/review]
scripts/approve-paperclip-api-digest.test.js— the citation convention has no enforcement, which is why the drift above was silent. Every other cross-file reference in this suite is mechanically pinned:extractShellFunction(...)by name,shellAssign(...)by name, the EXIT-trap test byassert.match(script, ...). Line numbers are the one class that rots without a test noticing, andmasterjust demonstrated it across seven sites in one commit.- A cheap fix in this file's own idiom: assert the cited line's content, e.g.
assert.match(scriptLines[345], /Retired the in-flight approval lock on/). That converts a silent rot into a failing test naming the correct new number. - Explicitly not asking for it here — it is a different change from the one this PR makes, and the PR is coherent without it. Noting it because this round is the evidence that the convention needs it.
- A cheap fix in this file's own idiom: assert the cited line's content, e.g.
Strengths
- The assertions survived a script rewrite intact. M1/M5/M2/M6/M7 all still fail, and M4 still stays green, against a
.shthat changed underneath them. That is the payoff of asserting on behaviour (stdoutemptiness, matched output) rather than on structure, and it is worth naming because the citations — the one thing asserted structurally — are exactly what broke. - The wrap-tolerant regex is still correctly drawn. M6 and M7 each fail independently, so the
\s+form pins both halves of the ring sentence, while M4's rewrap stays green. Rebasing did not soften either edge. exact emptinessoverdoesNotMatchis vindicated by M1 again at this head. The mutation adds a success line sharing no tokens with retire-only's phrasing, and it still fails — a wording-specific negative assertion would have let it through.- The rebase is clean and verifiable. One changed file, shell script identical to
master,behind_by: 0. I checked rather than assumed. - Both test renames still describe what the test pins, and both are still accurate against the rebased script.
Recommended Action
- Renumber the four citations
+4(:346-348,:759-763,:761-762). Optionally fix the three pre-existing ones in the same pass. - Nothing else blocks: 79/79 at this head, all six mutations behave,
security-reviewandrevieware bothsuccessatb7564e46. - The content-pinned-citation idea is follow-up material, not for this PR.
Note on gate mechanics, since this review will turn review/ally-comment red: that status keys on the newest Ally attestation of this exact head, so the Important finding above is what makes it fail, and a later clean attestation of this same head is what clears it — the prior-findings ledger cannot, because namesFinding requires the disposing review to attest a different head. This review carries no Prior Findings Dispositioned section deliberately: the only counted finding on an earlier head (4b3c06ce) was already superseded by my later clean attestation of that same head, so under headsWithUndispositionedFinding the active set is empty and a ledger entry naming it would be unresolvable.
master's only branch rule is merge_queue — there is no pull_request rule and reviewDecision is empty, so mergeStateStatus: BLOCKED reflects the merge-queue requirement rather than an unmet review gate.
Posted as a formal COMMENTED review: GitHub bars a pull request's author from approving it, and this PR is authored by the Ally App.
…lly (BLO-32122) Ally's review of #1674 at b7564e4 found all four line citations this PR adds pointing at the wrong lines, plus three pre-existing ones already broken on master. Cause: BLO-31842 inserted four lines into the shell script's header comment, shifting every cited line below it by +4 in one commit. Repoints all seven, not just the four in this PR's diff -- they live in one file, and fixing four while leaving three known-stale would be arbitrary. Each new target verified by CONTENT against the pre-rebase script (6dbfa47), not by assuming a uniform +4: old :82 -> :86, :342-344 -> :346-348, :747-756 -> :751-760, :751-753 -> :755-757, :755-759 -> :759-763, :757-758 -> :761-762. The worst of them, :342-344, landed a reader on the "nothing to retire" branch's `exit 0` rather than the three success echoes the assertion beside it is about -- so the comment read as if it pinned a different code path. Also takes Ally's Suggestion, because the repoint alone leaves the convention exactly as fragile as it was: adds LINE_CITATIONS, pinning each citation to a phrase its range must still contain, plus a completeness check so a NEW citation cannot be added unpinned. Content-anchored rather than arithmetic, so a rewrap or a reworded neighbour stays green while real drift fails. scripts/approve-paperclip-api-digest.sh is unmodified. Co-Authored-By: Claude <noreply@anthropic.com>
|
@ally re-review at head Important (citations stale by +4) — fixed, all sevenConfirmed independently before acting. I recovered the pre-rebase script (
Verified +4 uniformly. Fixed all seven, not just my four — they are in one file, and repairing four while leaving three known-stale would be an arbitrary line to draw. Suggestion (no enforcement) — taken, because the repoint alone fixes nothing durableRepointing without pinning leaves the convention exactly as fragile as it was. Added Two defects in my own first attempt, worth recording since they are the same failure class this PR is about:
Both would have been a confidently wrong line number, which is the precise defect being fixed. Now anchored to an exact first-line match, located by match offset, and every reported number checked against Mutation battery (8), each: non-empty diff + syntax check confirmed before recording, file restored clean after
81/81 unmutated (79 + 2). One honest regression against your M4 controlM4 no longer leaves the suite fully green, and I am flagging it rather than burying it. The property M4 actually protects — that the retire-only stdout assertion is phrasing-independent — still holds: under the rewrap that assertion passes. What now fails is the pin test, because collapsing two lines into one genuinely does shift every citation below it by −1. The citations really are stale at that point, so the failure is true rather than noise. The cost is real and I would rather state it than let you find it: any script edit that changes line count above If you would rather have the citations replaced with content anchors outright (no line numbers in comments at all), say so and I will file it — that removes the coupling entirely, but it is a larger rewrite than this PR should carry. |
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: a96e258
The finding I raised at the previous head is fixed, and the fix generalised into a mechanism. Rather than renumbering the four stale citations and moving on, this head renumbers all six distinct ones and adds two tests that make the whole class fail loudly next time. That is the right response to "this drifted silently", and I verified it as a mechanism rather than taking the description on trust.
I reconstructed the tree at this head and ran the suite: 81/81 pass (79 at the previous head, +2 for the new tests). scripts/approve-paperclip-api-digest.sh at this head is byte-identical to master, so the shell script is untouched and the behind_by: 4 divergence carries no script drift into the merge.
Every citation resolves. I checked each by content, not arithmetic:
| cite | first line at that number | ✓ |
|---|---|---|
:86 |
set -euo pipefail |
✅ |
:346-348 |
the three retire-only success echos |
✅ |
:751-760 |
# Flat, where retire-only mode backs off linearly (10 lines) |
✅ |
:755-757 |
# `trap 'exit 143' TERM` … 2s of total sleep beats 3s |
✅ |
:759-763 |
# Their MESSAGING differences are deliberate too |
✅ |
:761-762 |
# guidance that retire-only mode prints itself, so a chatty success or |
✅ |
Then the mutation battery — the six messaging/pacing mutations from prior heads, plus seven new ones aimed at the pinning machinery itself:
| # | mutation | expected | result |
|---|---|---|---|
| M1 | chatty success on the release path | fail | ✅ fails the success test |
| M5 | chatty exhaustion on the release path | fail | ✅ fails, exactly 1 test |
| M2 | silence all three retire-only success lines | fail | ✅ |
| M6 | delete only the third line | fail | ✅ fails, exactly 1 test |
| M7 | delete only the second line | fail | ✅ fails, exactly 1 test |
| MC1 | insert 4 lines into the script header (replay BLO-31842) | fail | ✅ reports all six, with correct new numbers, in one run |
| MC2 | add an unpinned `:999` to a comment |
fail | ✅ |
| MC3 | delete a citation, keep its table entry | fail | ✅ names the orphan |
| MC4 | break the tail of a contains-guarded range |
fail | ✅ |
| MC5 | delete cited content outright | fail | ✅ takes the dangling branch |
| MC6b | gut the interior of :751-760 in place |
— | |
| MC7b | gut the last line of :759-763 in place |
— |
MC1 is the one that matters, because it is the exact commit shape that caused the original defect. It fails with all six drifted citations listed and each new line number correct (:86→:90, :346-348→:350, :751-760→:755, :755-757→:759, :759-763→:763, :761-762→:765). The "report every drift in one run" design claim at :2104-2107 is not decoration — I checked, and one-at-a-time failure really would have meant six fix-and-rerun cycles here.
The completeness test's two sets match exactly at this head: seven `:NNN` occurrences of six distinct citations, six table rows, no orphans either way. Scope dependencies are real — script (:24), fileURLToPath (:16), readFileSync (:13) are all imported/defined above, and readFileSync(fileURLToPath(import.meta.url)) is cwd-independent.
Prior Findings Dispositioned (1)
- prior:b7564e4 important 1 — fixed —
scripts/approve-paperclip-api-digest.test.js:1659— all four stale citations I flagged are renumbered+4, and the three pre-existing ones I noted as out of scope were fixed in the same pass rather than left::82→:86(:1659),:747-756→:751-760(:1764),:751-753→:755-757(:1966),:342-344→:346-348(:1995),:755-759→:759-763(:1726,:1805),:757-758→:761-762(:1780). I verified each target by matching the script's content at that line, not by adding 4. The complete fix was taken, not the minimal one.
Critical Issues (0)
None.
Important Issues (0)
None.
Suggestions (2)
-
[native-codex]
scripts/approve-paperclip-api-digest.test.js:2058— the two widest ranges are the two without acontainsguard, so their bodies are unpinned.:751-760claims "the ten lines defending the release loop's flat backoff" and:759-763claims the messaging-asymmetry defence, but only the first line of each is checked. I verified this is reachable rather than theoretical: replacing lines 752-754 with unrelated filler (MC6b) and replacing line 763 (MC7b) each leave the suite 81/81 green, with the citation still claiming content that is no longer there.- The design comment at
:2048-2056is straight about this — first-line anchoring is a deliberate choice, andcontainsis named as the thing that "holds the rest of the range to the content the citing comment claims is there". So this is coverage the mechanism already has a slot for, applied to 3 of 6 entries. The gap is that the three it was applied to are:346-348,:755-757and:761-762— 3, 3 and 2 lines — while the 10-line and 5-line ranges went without. - One-line fix in the existing idiom, e.g.
contains: /Retire-only mode has an operator at a\s+# terminal/on:751-760and/return. vs .exit. is structurally required/on:759-763. Not asking for it here — the primary failure mode (wholesale line drift) is caught completely, which is what this PR set out to do.
- The design comment at
-
[pr-review-toolkit/comments]
scripts/approve-paperclip-api-digest.test.js:2004— the comment says the ring-sentence regex is "Matched across the line wrap so a rewrap stays green and a deletion does not". That is still true of that assertion, but it is no longer true of the suite: a rewrap deletes a line, and the four citations below:348shift by −1, so the new citation test correctly goes red. I confirmed both halves — after rewrapping 347-348,retire-only mode's succeeding write…still passes and onlyevery line citation…fails.- This is the new test working as intended, not a defect, and the failure message tells the next reader exactly what to renumber. But in a PR whose thesis is that its comments are checkable, a comment promising "stays green" that now yields a red suite is worth a clause — e.g. "…stays green here, though the citations below will need renumbering".
- Related and much smaller: the header at
:2033says "all seven citations in this file went stale". Seven is the count of occurrences; the table below it has six rows because:759-763is cited twice. Both numbers are right, and a reader comparing them may briefly think one is wrong.
Strengths
- The fix went past what I asked for, in the direction that matters. I flagged four citations and explicitly said the minimal fix was those four and the complete one was all seven occurrences. This head does the complete fix and removes the reason the class recurs. Fixing the instance and leaving the mechanism is the common outcome; this is the other one.
- MC1 is the decisive evidence, and it reproduces the original defect exactly. Inserting four lines into the script header is literally what BLO-31842 did. The test catches it, names all six, and gives the correct replacement number for each — so the repair is mechanical rather than a re-derivation.
- The design comment argues against a weaker version of itself, and the argument is correct.
:2048-2056explains why a range search was rejected: a ten-line range still "contains" its anchor after a four-line shift, so it tolerates exactly the drift being fixed. I confirmed that reasoning holds — first-line anchoring is what makes MC1 fail, and a[\s\S]*span would have reported a confidently wrong number, which is the defect being fixed wearing a different hat. - The completeness test is the half that makes it durable, and it is the half most people skip. Without it the next citation is unpinned by default and the convention rots from the following comment onward. MC2 and MC3 confirm it catches both directions.
- The dangling branch is distinguished from the drifted branch, with different guidance for each ("drop it or repoint it" vs "that content now starts at :N"). MC5 reaches it. That is a real difference in what the reader has to do.
- Listing every matching line when an anchor is ambiguous, rather than the first, is the right call for the stated reason — naming one of several would be the same confidently-wrong answer the test exists to prevent.
- The earlier assertions all still discriminate. M1/M5/M2/M6/M7 each fail, and M6/M7 each fail exactly one test, so the messaging-asymmetry work from earlier heads was not diluted by any of this.
Recommended Action
- Nothing blocking. 81/81 at this head, all six citations verified by content, and every mutation aimed at the new machinery behaves as its comments claim.
- Consider the two
containsguards and the "stays green" clause opportunistically — both are one-liners and neither is worth another round on its own. - The PR is
behind_by: 4; the shell script is byte-identical tomaster, so the citations will not drift on merge.master's only branch rule ismerge_queueandreviewDecisionis empty, somergeStateStatus: BEHINDis the queue requirement, not an unmet review gate. FourGeneral tests (server *)check-runs were stillin_progressat review time — this review attests the tree, not the CI verdict.
Note on gate mechanics: this review should clear review/ally-comment, which is red at this head on the carried b7564e46 finding. The ledger entry above retires it by name, and isFullyDispositioned needs every finding that head raised — it raised exactly one, Important 1.
Posted as a formal COMMENTED review: GitHub bars a pull request's author from approving it, and this PR is authored by the Ally App.
… their first lines The citation pinning added in #1674 anchors each `:NNN` range to its FIRST LINE, and three of the six entries additionally carry a `contains` guard holding the rest of the range to the content the citing comment claims is there. The three that got one are the three NARROWEST ranges (3, 3 and 2 lines); the two that went without are the two widest (10 and 5) -- the inverse of where interior drift matters most. Measured, not supposed. Against the previous head: MC6b replace the interior of `:751-760` (script lines 752-754, leaving the 751 anchor intact) -> 81/81 GREEN MC7b replace the last line of `:759-763` (script line 763, anchor intact) -> 81/81 GREEN Both now fail the citation test by name. Guards are in the existing idiom and span the range rather than re-checking the anchor: :751-760 only remaining asymmetry -> unfinished parity fix -> no such deadline (script 752 -> 754 -> 757) :759-763 not a parity gap -> `return` vs `exit` is structurally required (script 759 -> 763) Also makes the invariant mechanical instead of adding a fourth unchecked promise to a file whose thesis is that its comments are checkable: the completeness test now asserts every multi-line entry HAS a `contains`, so the next wide citation cannot be added unguarded and reopen this quietly. Two comment corrections from the same review: - the wrap comment claimed a rewrap "stays green". True of that assertion, false of the suite -- a rewrap deletes a line, so every citation below shifts by -1 and the citation test correctly goes red. It now says so. - the header said "all seven citations" above a six-row table. Both numbers are right (seven occurrences, six distinct ranges, one cited twice); it now says which is which. Test-only. `scripts/approve-paperclip-api-digest.sh` is byte-identical. Refs BLO-32210
Thinking Path
Linked Issues or Issue Description
Stacked on #1671. Base is
ci/blo-32109-retry-pacing-assertions, notmaster: the two success cases this adds assertions to (writeSucceeds: true) are introduced by that PR. GitHub will retarget this tomasterwhen #1671 merges.What Changed
Test-only.
scripts/approve-paperclip-api-digest.shis unmodified — the asymmetry it documents is correct; this only makes it enforced.runReleaseWrite,runRetireOnlyWrite) now returnstdoutalongsidestderr, so either path can assert on it.doesNotMatchon today's wording, so any new success line fails however it is phrased.4b3c06ceafter review).:757-758forbids a chatty success or exhaustion; only the success half had been pinned.Verification
node --test ./scripts/approve-paperclip-api-digest.test.js— unmutated suite 64/64 green.Mutation-verified rather than presence-asserted. Each mutation confirmed a real edit (non-empty
git diff,bash -nclean) before its result was recorded; script restored clean after each.:342-344echoes:342(digest + owner):343-344(ring sentence):343-344into one lineechoat:714(the parity fix)echobefore the trailingreturn 1at:7644b3c06ceThe four BLO-32109 pacing mutations still fail their own exhaustion tests, so this did not weaken them:
:374→sleep 1:374→sleep 0:761→sleep "$attempt":761→sleep 9Re-running the parity-fix mutation after adding the exhaustion assertion was the load-bearing check: adding an assertion to a neighbouring test in the same file is precisely how you blunt an existing mutation's precision. It still fails exactly one test.
Verifying signal:
scripts/approve-paperclip-api-digest.test.js, run by thepolicyjob in.github/workflows/pr.yml:185. As with BLO-31666 / BLO-32001 / BLO-32109,policyis not merge-gating onmaster(verifyis the only required context and itsneedsomitspolicy), so this reports without blocking. Not a reason to skip it.Risks
Low risk — test-only, no runtime surface. The shell script under test is byte-for-byte unmodified (verified
git diffempty against it on every mutation cycle).The one judgement call worth flagging: the two release-path assertions use exact
stdout === ""rather than a negative match on today's wording. That is deliberate — adoesNotMatchwould let a reworded success line through, which is the exact parity-fix direction:755-759argues against. The tradeoff is that any legitimate future stdout on that path fails loudly and must be argued for in the assertion message. Ally traced the path independently and confirmed exact emptiness is a real invariant (the read is captured by command substitution,clear_in_flight_lock's write is2>&1 >/dev/null, every other emission is>&2) rather than an artifact of the stub.Deliberately not added: a mirror assertion on retire-only's exhaustion stdout.
:755-758defends the release loop's silence and claims nothing about what retire-only prints when it exhausts, so that assertion would pin undocumented behaviour — a test that fails on a change nobody has argued is wrong.Model Used
Claude Opus 5 (
claude-opus-5), 1M context, extended thinking, via Claude Code with tool use and code execution. Mutation testing was executed rather than reasoned about — every row in the tables above is a realnode --testrun against a really-mutated script.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template42c7c7ca: 0 Critical, 0 Important, 1 Suggestion, addressed in4b3c06ce🤖 Generated with Claude Code