test(heartbeat): add Ally exact-head re-review replay test (BLO-17518) - #784
Conversation
Replays the full BLO-17456 incident chain end to end in one suite: old-head review -> fixes pushed to new head -> stale review fails to satisfy the gate -> fresh review-request run leaves no durable evidence -> durable-evidence retry (PR #767) makes it retry-eligible instead of stranding -> dispatch fairness keeps it from starving behind a backlog of unrelated pr_review wakes -> exact new-head review lands and flips review/ally-complete to success. Calls the real exported heartbeat.ts functions (shouldScheduleAutomaticRunRetry, evaluatePrReviewCompletionEvidence, selectAgedPrReviewRunForFairDispatch) directly, and fixtures pim-multicast-gateway's require-ally-review.mjs head- attestation matching locally instead of hitting a live GitHub API. Verified by scratch-reverting each of the two production fixes independently: reverting the pr_review_output_missing retry branch or the fairness promotion each break a distinct assertion in this file.
1 similar comment
|
@ally please review — focus areas:
|
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: 73a89aa
Critical Issues (0)
None.
Important Issues (2)
- [gstack/review]
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:72— The fixture makesreview.commit_idpart of the exact-head decision. GitHub can rewrite that field after an Update branch operation, while the explicitReviewed head:body line is the immutable attestation. This test therefore cements the mutable behavior and has no case wherecommit_iddiffers but the body validly attests the current head. - [native-codex]
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:118— This does not exercise the webhook-to-dispatch chain described by the acceptance criteria. It constructs an already-normalized heartbeat context and invokes three pure helpers independently, so it still passes if webhook parsing drops the new head/task key, retry scheduling fails to enqueue, or the selected fairness ID is ignored by the production dispatch sort. Replay a synthetic webhook through the real normalization/queue boundary and assert the resulting retry/dispatch decision before evaluating gate success.
Suggestions (1)
- [tests]
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:221— The comment says it re-checks the old head against the same full history, but the assertion repeats the old-review-versus-new-head case. Use[oldReview, newReview]withOLD_HEADif that distinct invariant is intended.
Strengths
- The test uses the real exported heartbeat retry, evidence, and fairness helpers and includes explicit regression assertions for both fixes.
- The stale-old-head and fresh-new-head fixtures make the intended incident chronology easy to follow.
Recommended Action
- Address the Important issues before merge.
- Consider the assertion cleanup opportunistically.
|
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 |
Address Ally's #784 review by treating the explicit Reviewed head body line as the immutable review-gate attestation, adding coverage for mutable review.commit_id values, and asserting the fresh-head GitHub webhook normalization plus reviewer queue keys before the retry/fairness replay. Also correct the old-head history assertion so it checks the full review history against the old head instead of repeating the old-review/new-head case. Verification: - PATH=/opt/homebrew/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/pkg/env/global/bin:/opt/X11/bin:/Library/Apple/usr/bin:/Applications/Wireshark.app/Contents/MacOS:/usr/local/go/bin:/opt/homebrew/bin:/Users/oramadan/.codex/tmp/arg0/codex-arg0YtMLbB:/Users/oramadan/go/bin:/Users/oramadan/.opencode/bin:/opt/homebrew/opt/curl/bin:/opt/homebrew/opt/python@3.12/libexec/bin:/Users/oramadan/Library/Python/3.9/bin:/Users/oramadan/.bun/bin:/Users/oramadan/.rustup/toolchains/stable-x86_64-apple-darwin/bin:/usr/local/opt/ansible@9/bin:/Library/Frameworks/Python.framework/Versions/3.12/bin:/Users/oramadan/.cargo/bin:/Applications/iTerm.app/Contents/Resources/utilities:/Users/oramadan/.local/bin:/Users/oramadan/src/chromium.googlesource.com/chromium/tools/depot_tools:/Users/oramadan/.maestro/bin:/Users/oramadan/.lmstudio/bin pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-pr-review-gate-replay.test.ts --reporter=dot - PATH=/opt/homebrew/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/pkg/env/global/bin:/opt/X11/bin:/Library/Apple/usr/bin:/Applications/Wireshark.app/Contents/MacOS:/usr/local/go/bin:/opt/homebrew/bin:/Users/oramadan/.codex/tmp/arg0/codex-arg0YtMLbB:/Users/oramadan/go/bin:/Users/oramadan/.opencode/bin:/opt/homebrew/opt/curl/bin:/opt/homebrew/opt/python@3.12/libexec/bin:/Users/oramadan/Library/Python/3.9/bin:/Users/oramadan/.bun/bin:/Users/oramadan/.rustup/toolchains/stable-x86_64-apple-darwin/bin:/usr/local/opt/ansible@9/bin:/Library/Frameworks/Python.framework/Versions/3.12/bin:/Users/oramadan/.cargo/bin:/Applications/iTerm.app/Contents/Resources/utilities:/Users/oramadan/.local/bin:/Users/oramadan/src/chromium.googlesource.com/chromium/tools/depot_tools:/Users/oramadan/.maestro/bin:/Users/oramadan/.lmstudio/bin pnpm --filter @paperclipai/server typecheck - git diff --check Co-Authored-By: Paperclip <noreply@paperclip.ing>
|
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: 56dc3de
Prior Findings Dispositioned (2)
- prior:73a89aa important 1 — fixed —
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:296— The current-head regression explicitly verifies that a review body attestingNEW_HEADremains valid when GitHub rewritesreview.commit_idtoOLD_HEAD. - prior:73a89aa important 2 — still-present —
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:142— The webhook context is normalized, but the heartbeatcontextSnapshotis still assembled manually; no production enqueue or dispatch boundary consumes the normalized output.
Critical Issues (0)
None.
Important Issues (2)
- [prior:73a89aa important 2]
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:142— The replay still does not exercise the webhook-to-dispatch chain described by the acceptance criteria. Because it manually constructs an already-normalized heartbeat snapshot and invokes independent helpers, it will pass if the webhook route drops the new head/task key while enqueueing, retry scheduling fails to create a run, or dispatch ignores the fairness selection.- Drive a synthetic
pull_request.synchronizeevent through the route's enqueue boundary with dependencies stubbed, then assert that the produced reviewer run carriesNEW_HEAD, the PR task key, and the expected idempotency key before applying retry and fairness behavior.
- Drive a synthetic
- [tests]
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:248— The claimed backlog-ahead starvation scenario is not represented.freshRetryRunis older than both unrelated reviews, so it is already the oldest eligible review and the assertion only proves ordinary oldest-aged selection, not eventual recovery when unrelated reviews are genuinely ahead.- Model repeated dispatch with older unrelated review runs and show that the target retry is eventually selected, or narrow the test and comments to the oldest-aged selection behavior actually covered.
Suggestions (0)
None.
Strengths
- The rewritten-
commit_idregression now captures the immutable body-attestation contract directly. - The test uses the real exported webhook normalization, retry classification, evidence evaluation, and fairness-selection helpers with deterministic incident fixtures.
Recommended Action
- Address the Important issues before merge.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
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: 3f703b4
Prior Findings Dispositioned (2)
- prior:73a89aa important 2 — still-present —
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:143— The test now calls the same wakeup-options builder used by production, but it still invokes that helper directly and fabricatesproducedReviewerRun; no route invocation observesheartbeat.wakeup, no retry is persisted, and no dispatch consumes the selected fairness ID. - prior:56dc3de important 2 — fixed —
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:270— Two unrelated PR-review runs now have earliercreatedAtvalues than the target retry, and the assertions at lines 286-303 show both older runs taking their turns before the target is selected.
Critical Issues (0)
None.
Important Issues (1)
- [prior:73a89aa important 2]
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:143— The replay still does not exercise the webhook-to-enqueue-to-retry-to-dispatch chain it claims to cover. Because the test directly calls decision helpers and constructs the resulting run in memory, it passes if the route fails beforeheartbeat.wakeup, retry eligibility is never converted into a queued retry, or production dispatch ignores the selected fairness run.- Drive a signed synthetic webhook through the route with the wakeup boundary stubbed, assert the captured/persisted run carries the exact head and task key, then exercise retry creation and dispatch consumption rather than only their predicates.
Suggestions (0)
None.
Strengths
- The wakeup payload extraction is shared with the production
heartbeat.wakeupcall, reducing drift between route behavior and fixture assertions. - The revised queue fixture now models genuinely older unrelated review work and preserves the no-consecutive-review guard.
- Exact-head body attestation remains correctly independent of GitHub's mutable
review.commit_id.
Recommended Action
- Address the remaining Important issue before merge.
…te-replay-test # Conflicts: # server/src/routes/github-webhook.ts
…route Ally's remaining Important finding on #784 was that the replay claimed to cover webhook -> enqueue -> retry -> dispatch but actually called four pure helpers side by side and fabricated the reviewer run in memory: it would still pass if the route dropped the new head or task key while enqueueing. Drive a signed synthetic `pull_request.synchronize` through the Express route against a real database instead, and make every later stage consume the row the route persisted: - step 3b asserts the persisted heartbeat run + wake carry the exact NEW head, the PR task key, and the delivery-scoped idempotency key, - step 4 feeds that persisted contextSnapshot to the evidence evaluator and the retry gate, - step 4b runs fairness selection over the real queued row (by its own id), aged relative to its actual createdAt, - step 5-6 posts the review against the head the route recorded. The pure review-gate fixture assertions move to their own describe so they still run where embedded Postgres is unavailable. Also fixes the idempotency-key expectation, which master changed to a delivery-scoped suffix in BLO-18953 (#833), and resolves that same file's merge conflict against master. Revert-checked, all against the persisted-row chain: - `pr_review_output_missing` off the retry allowlist (PR #767) -> step 4 fails - `selectAgedPrReviewRunForFairDispatch` no-op -> step 4b fails - route pinning githubHeadSha to the stale head -> steps 3b and 5-6 fail BLO-17518
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: ffaeb9a
Prior Findings Dispositioned (1)
- prior:73a89aa important 2 — still-present —
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:222— Materially narrowed, but not closed. The enqueue half is now genuinely covered::290posts a signed syntheticpull_request.synchronizethrough the real Express route against embedded Postgres, and:311/:322assert the route persisted exactly oneheartbeat_runsrow and oneagent_wakeup_requestsrow, so the "route fails beforeheartbeat.wakeup" failure mode is caught. The other two named failure modes are unchanged::222setsskipQueuedRunDispatch: true, so no production dispatch runs;:385asserts only theshouldScheduleAutomaticRunRetrypredicate and never observes a retry row being created;:435asserts only that the fairness selector returns the id, with nothing consuming that selection.
Critical Issues (0)
None.
Important Issues (1)
- [prior:73a89aa important 2]
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:222— The suite name at:176advertiseswebhook -> enqueue -> retry -> dispatch, butretryanddispatchare predicate-level only. The test still passes if retry eligibility is never converted into a queued retry row, or if production dispatch ignores the run the fairness selector picked.- Either extend step 4 to assert a retry run is actually persisted and step 4b to let the real dispatch path consume the selection, or narrow the suite name and the header comment's
retry -> dispatchclaim to "retry/dispatch eligibility" so the advertised scope matches the delivered coverage. The latter is cheap and would close this out.
- Either extend step 4 to assert a retry run is actually persisted and step 4b to let the real dispatch path consume the selection, or narrow the suite name and the header comment's
Suggestions (2)
- [code]
server/src/routes/github-webhook.ts:2542—__test_buildPrReviewerWakeupOptionshas no consumer at this head. The previous revision's test called it directly; the rewrite to route-driven testing orphaned it. Drop the export. - [types]
server/src/routes/github-webhook.ts:1853—String(reviewerWakeupOptions.payload?.taskKey ?? "")is unreachable defensive code (PrReviewerWakeupOptionsdeclarespayloadnon-optional withtaskKey: string). If it ever were reached, the""fallback would silently become thewithPrReviewerTaskLockkey and thefindActivePrReviewerForTasklookup key rather than failing loudly. Read the typed field directly.
Strengths
- The replay is now a real integration test rather than a helper-call sequence: the run and wake rows under assertion are the ones the route actually persisted, and every later step consumes that row.
- I verified the three regression guards the header comment claims, and they hold. Reverting the
pr_review_output_missingallowlist branch (server/src/services/heartbeat.ts:711) fails step 4; revertingselectAgedPrReviewRunForFairDispatchfails step 4b; dropping the head/task key from the enqueue fails step 3b. - The fairness fixtures are well-formed, not vacuous:
isPrReviewRetryContextaccepts eitherreviewKindor apr_review:-prefixedtaskKey, so the older unrelated backlog rows genuinely participate in selection and the ordering assertions are load-bearing. - The
buildPrReviewerWakeupOptionsextraction is behaviour-preserving —contextis only read between the new eager build site and the deferredheartbeat.wakeup, so hoisting the payload out of the closure changes nothing observable. - Exact-head attestation remains correctly independent of GitHub's mutable
review.commit_id, and the deliberate choice to leave the seeded BLO-17456 issue unassigned so the delivery yields exactly one run is documented at the fixture.
Recommended Action
- Address the remaining Important issue before merge — narrowing the advertised scope is a sufficient fix if wiring up real retry/dispatch consumption is out of scope for this PR.
- Consider the two Suggestions opportunistically.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
allyblockcast
left a comment
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: 06e31d6
Looks good. No Critical or Important blockers remain at this head, and the one carried-forward finding is closed.
Prior Findings Dispositioned (1)
- prior:73a89aa important 2 — fixed —
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:178— The finding was a scope-claim mismatch: the suite advertisedwebhook -> enqueue -> retry -> dispatchwhile retry and dispatch were predicate-level only. This head takes the narrowing remedy the last round offered. The suite name at:178now readswebhook -> enqueue -> retry/dispatch eligibility -> gate success; the header at:22matches, and:36-38states outright that "Retry and dispatch are asserted at the eligibility boundary against that persisted row rather than by running a live adapter dispatch worker." The skip warning at:173and the test names at:373("is retry-eligible") and:395("becomes dispatch-eligible") agree.:224still setsskipQueuedRunDispatch: true, which is now accurately disclosed rather than over-claimed. Advertised scope and delivered coverage line up.
Suggestions (2)
- [gstack/review]
server/src/routes/github-webhook.ts:1853—String(reviewerWakeupOptions.payload?.taskKey ?? "")is statically unreachable:PrReviewerWakeupOptionstypespayloadas non-optional withtaskKey: string, andbuildPrReviewerTaskKeyalways returns a non-empty template string. The concern is the failure shape if that ever regresses —?? ""coalesces silently, and the empty key then flows intowithPrReviewerTaskLock(:1863),findActivePrReviewerForTask(:1898) andselectPrReviewerAgentId(:1899), collapsing every PR onto one shared lock and coalescing key. CallingbuildPrReviewerTaskKey(context)directly, or throwing on empty, fails loudly instead. - [native-codex]
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:45— The gate fixture is pinned torequire-ally-review.mjs @ 12a0f903in a separate repo, with no drift detection. The comment already asks future editors to re-sync on divergence, and the AC explicitly wanted fixturing over a live API, so this is fine as shipped — but if that script's matching changes, this suite keeps passing against obsolete semantics. Worth a periodic cross-repo check if the gate keeps evolving.
Strengths
- The enqueue half is real coverage, not a mock ladder:
:290-298posts an HMAC-signedpull_request.synchronizethrough the actual Express route against embedded Postgres, and:311/:322assert the route persisted exactly oneheartbeat_runsrow and oneagent_wakeup_requestsrow. Downstream steps consume those persisted rows rather than fixtures, so:334-352genuinely pins the queued work toNEW_HEAD. buildPrReviewerWakeupOptionsis a clean behavior-preserving extraction. Payload andcontextSnapshotfield sets are identical to the previous inline object, the builder is pure over(context, eventName, deliveryId), and hoisting it above the task lock captures nothing that mutates inside —reviewerAgentIdis still passed separately at the:1913call site, which is the only reviewer wake site.- The fixture fails closed in the right places:
extractReviewedHeadSharequires exactly one attestation and exactly one mention, so an ambiguous body reads pending rather than success, and the module-level/gregexes are only ever used viamatchAll(which clones and never advanceslastIndex), so there is no cross-call state bug. - Test isolation is sound — each case seeds a fresh company/agent under a new UUID and filters queries by
agentId, so leakage across cases cannot manufacture a false pass.
Recommended Action
- No Critical issues.
- No Important issues — the prior blocker is closed.
- Consider the two Suggestions opportunistically; neither blocks merge.
Resolve the reviewer wake conflict by keeping the shared wakeup option builder while preserving master delivery accounting and queued/declined wake handling. Verification: - pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-pr-review-gate-replay.test.ts src/__tests__/github-webhook.test.ts --reporter=dot --no-file-parallelism --maxWorkers=1 - pnpm --filter @paperclipai/server typecheck - git diff --check Co-Authored-By: Paperclip <noreply@paperclip.ing>
allyblockcast
left a comment
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: d1de4cc
Looks good. No Critical or Important blockers at this head, and no prior finding remains open. This is a merge-conflict refresh, so I reviewed it as one — I re-derived the conflict resolution against master rather than re-reading the test, and the resolution is faithful.
Critical Issues (0)
None.
Important Issues (0)
None.
Suggestions (2)
- [gstack/review]
server/src/routes/github-webhook.ts:2660— The branch is 66 commits behindmaster(merge base5d3992f9, 16:10Z), andmasterhas since churned the adjacent author-wakecontextSnapshot: PR #865 addedgithubReviewFeedbackCommentIdatmaster's:2646, roughly 15 lines below the block this PR touches. That field is legitimately absent here — I confirmed it is not in the merge base, so this is staleness and not a dropped resolution — but the next merge will land in the same object literal. Worth re-merging before this sits much longer. - [native-codex]
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:45— Carried forward, unchanged: the gate fixture is pinned torequire-ally-review.mjs @ 12a0f903in a separate repo with no drift detection. Fine as shipped (the AC wanted fixturing over a live API, and the comment asks future editors to re-sync), but if that script's matching changes this suite keeps passing against obsolete semantics.
Strengths
- The conflict resolution does what the description claims. Diffing this head against
mastertip forgithub-webhook.tsyields exactly three hunks: the two intended (builder addition at:1351, call-site collapse at:1999/:2068) plus thegithubReviewFeedbackCommentIdstaleness above. Nothing frommasterwas silently reverted. - Delivery accounting survived intact, which was the specific risk in this conflict. Both counter sites are present and correctly placed —
receivedat:2069after the last suppression gate,queuedat:2086gated on a truthywakeResult— and the declined-wake branch still treats anullresult as not success, logs the gate-declined warning, and returnsfalseso the 200 body cannot claimreviewerWakeFired. That is the BLO-18859/BLO-18847 invariant, unchanged. - The eager hoist is still behavior-preserving after the merge. This was the real merge-interaction hazard:
buildPrReviewerWakeupOptionsis now called at:2002but consumed at:2071, andmasteradded code in between. I read that span —contextis only ever read (wakeReason,prNumber,repoFullNamein logs, andprReviewerWakeIdempotencyScope), never mutated, andreviewerAgentIdis resolved separately and passed as the first argument rather than captured in the options. So hoisting captures nothing that changes underneath it. - The test is genuinely untouched, not just apparently so. The replay file is byte-identical between the previously-reviewed head and this one, so the scope-claim narrowing at
:178and the header disclosure at:36-38that closed the last outstanding blocker are intact rather than re-litigated. - Both prior Suggestions were taken, and taken properly.
__test_buildPrReviewerWakeupOptionsis gone (no__test_export for it remains at:2726-2746), and:2002now readsreviewerWakeupOptions.payload.taskKeydirectly instead ofString(... ?? ""). BecausePrReviewerWakeupOptionstypespayloadas non-optional withtaskKey: string, the silent-""failure shape I flagged — an empty key collapsingwithPrReviewerTaskLock,findActivePrReviewerForTask, andselectPrReviewerAgentIdonto one shared key — is now unrepresentable rather than merely unreachable.
Recommended Action
- No Critical issues.
- No Important issues.
- Consider the two Suggestions opportunistically; neither blocks merge.
Note on CI, not a review finding: Verify serialized server suites (1-4/4) are still pending, which is what makes mergeStateStatus UNSTABLE — everything else is green. Those are the jobs that actually exercise the embedded-Postgres half of this replay (the author's local run skipped it), so let them land before merging. Approval does not bypass required checks.
allyblockcast
left a comment
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: d1de4cc
Looks good. No Critical or Important issues at this head. This push is a rebase onto a substantially changed master plus the two suggestion fixes from the last round, so I focused on the integration seam rather than re-reviewing the unchanged test body.
Suggestions (1)
- [tests]
scripts/run-vitest-stable.mjs:31— This suite is not inadditionalSerializedServerTestsand its filename does not matchrouteTestPattern, so it runs in the general-server lane. That is safe today and it passed there, but it is the only embedded-Postgres suite that drives an Express route while issuing cluster-wide statements inbeforeEach—UPDATE "heartbeat_runs" ...(server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts:196) andTRUNCATE TABLE "companies" CASCADE(:198). Its siblings (heartbeat-dependency-scheduling,heartbeat-issue-liveness-escalation,heartbeat-process-recovery) are all on the allowlist. The isolation currently rests on two implicit invariants: the server vitest config pinsmaxWorkersto 1, and each file gets its own temp database. Neither is stated at the truncation site. Worth either adding the file to the allowlist or noting the dependency in a comment, so a future move to intra-shard parallelism does not turn this into cross-suite data loss.
Strengths
- The extraction is provably behaviour-preserving, which is the whole risk of hoisting the options build above the task lock.
contexthas a single binding (server/src/routes/github-webhook.ts:1861) and there is not one assignment tocontext.*anywhere in the 2746-line file, so the eagerly built payload cannot observe state that later mutates.buildPrReviewerWakeIdempotencyKeyis also invoked at the same point as before, so the timing of its defensive throw is unchanged rather than moved ahead of a suppression gate. - The rebase integration holds where it most plausibly would not. Master added
isSelfReviewedPrsince the last revision, but it is confined to the actionable-feedback reopen escalation (:2533) and never reaches the reviewer enqueue path (:2002–:2071), so it cannot suppress the wake this suite asserts. All fourheartbeat.jssymbols the test imports still resolve at this head (PR_REVIEW_QUEUE_FAIRNESS_MAX_WAIT_MS,evaluatePrReviewCompletionEvidence,selectAgedPrReviewRunForFairDispatch,shouldScheduleAutomaticRunRetry) despite master rewriting that file heavily. - Both prior suggestions are genuinely addressed, not just acknowledged: the task key is now read off the typed field at
:2003instead of through an unreachableString(... ?? "")coalesce that would have silently collapsed every PR onto one lock key, and the orphaned__test_buildPrReviewerWakeupOptionsexport is gone. PrReviewerWakeupOptionsderives its base fromParameters<ReturnType<typeof heartbeatService>["wakeup"]>[1]rather than restating the shape, so the builder cannot drift from the real service signature — the failure mode would be a compile error, not a wrong payload.- CI evidence actually covers the new file. Because it lands in the general-server lane, it ran in shards 1/4–4/4, all of which passed; the four pending checks are the serialized shards, which by the selection rules do not include this suite.
Recommended Action
- No Critical issues.
- No Important issues.
- Consider the lane-placement suggestion opportunistically; it does not block merge.
Thinking Path
This PR is a regression replay for BLO-17456/BLO-17518. The incident chain depended on three boundaries staying aligned: GitHub webhook normalization must preserve the current PR head and stable reviewer task key, Paperclip must retry a PR-review run that left no durable review evidence, and the review gate must accept only an explicit Ally attestation for the exact head being checked. It also verifies the important GitHub edge case where the durable body attestation remains authoritative even if the mutable review commit field points somewhere else.
What Changed
server/src/__tests__/heartbeat-pr-review-gate-replay.test.ts, a fixture-driven replay of the old-head review -> new-head push -> reviewer wake -> missing durable evidence retry -> fairness dispatch -> exact-head approval path.pim-multicast-gateway'srequire-ally-review.mjshead-attestation behavior locally instead of requiring a live GitHub API.review.commit_idbehavior by treating the explicitReviewed head:body line as the immutable exact-head attestation.Risks
require-ally-review.mjsfixture can drift frompim-multicast-gateway; the test comments call out the source commit and the fixture should be updated if that script changes.Model Used
Verification
PATH=/opt/homebrew/bin:$PATH pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-pr-review-gate-replay.test.ts --reporter=dot— 6/6 passed.PATH=/opt/homebrew/bin:$PATH pnpm --filter @paperclipai/server typecheck— passed.git diff --check— passed.Closes BLO-17518.