test(approvals): pin the ordering invariant the dead-run sweep rests on (#3456 follow-up) - #3719
Merged
Merged
Conversation
…on (#3456 follow-up) `releaseDeadRunRequests` recalls a pending request whose owning run is terminal, on the premise that such a pair can only be an orphan. That premise is not self-evident — it holds only because every in-band transition moves the request out of `pending` BEFORE handing the run back. Reverse that order and a run which finishes promptly afterwards becomes indistinguishable from an orphan, so the sweep would cancel a LIVE approval: precisely the failure mode it is built never to have. Nothing enforced the ordering. It was a convention spread across four public methods and seven resume/cancelRun call sites, any of which a refactor could reorder without a single test going red. So pin the invariant itself rather than any one method's call order: at the instant the run is handed back, no request owned by that run may still be `pending`. The probe records every resume/cancelRun hand-back and asserts it, across all seven sites — decide approve/reject, recall, send-back, send-back past the revision budget (the ADR-0044 auto-reject branch), recall inside the revise window (cancelRun), and resubmit. Verified load-bearing by a REORDER-ONLY mutation of `recall` — resume moved ahead of the finalise, end state identical. Exactly one test fails, the new one; the other 235 stay green. That is also the proof no existing test covered this ordering. Also points the `resolveRunDataContext` residual comment at #3712, which now tracks the schedule-triggered run that still cannot carry `flowRunId`. plugin-approvals 236 passed, service-automation 365 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JGXCuBt5mSXbN3Gc8yfbRv
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
The Check Changeset gate requires every PR to state its release intent explicitly, and offers an empty changeset as the way to say "nothing ships". This PR is tests plus one comment reference, so no package is bumped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JGXCuBt5mSXbN3Gc8yfbRv
os-zhuang
marked this pull request as ready for review
July 27, 2026 16:19
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #3703 (which closed #3456). Test-and-comment only — no behaviour change.
The unguarded assumption
releaseDeadRunRequestsrecalls a pending request whose owning run has reached a terminal state, on the premise that such a pair can only be an orphan. That premise is not self-evident. It holds only because every in-band transition moves the request out ofpendingbefore it hands the run back.Reverse that order anywhere and a run that finishes promptly after the hand-back becomes indistinguishable from an orphan — so the sweep would cancel a live approval. That is precisely the one failure mode the sweep was built never to have, and #3703 leaned on the ordering to justify putting
completedin the terminal set at all.Nothing enforced it. It was a convention spread across four public methods and seven
resume/cancelRuncall sites, any of which a refactor could reorder without a single test going red.What this adds
Rather than pin any one method's call order — which would just re-encode the convention in a second place — it pins the invariant itself: at the instant the run is handed back, no request owned by that run may still be
pending.The probe stands in as the automation surface, records every
resume/cancelRunhand-back with the run's still-pending requests at that moment, and asserts the set is empty. Seven scenarios cover all seven sites:decide(approve)/decide(reject)decideNodefinalisesrecallon a pending requestrecallinside the revise windowcancelRun(ADR-0044 — no reject edge to resume down)sendBacksendBackpast the revision budgetresubmitWhy I believe the test is load-bearing
Verified with a reorder-only mutation of
recall—resume/cancelRunmoved ahead of the finalise, end state identical:Exactly one failure, the new test. That is the result worth having: it shows the test catches pure reordering (not just a changed outcome), and — since the other 235 stayed green — that no existing test covered this ordering, which is the whole justification for adding it.
An earlier, cruder mutation that changed the terminal status instead broke a dozen unrelated tests and proved nothing about ordering; it was discarded.
Also here
resolveRunDataContext's residual comment now points at #3712, filed to track the remaining #3456 gap: a schedule-triggeredrunAs:'user'run with no trigger user passes no ObjectQL context, so it carries noflowRunIdand is still subject to the lock. Previously that residual lived only in a code comment, the changeset, and a merged PR body — nothing anyone would find.Verification
plugin-approvals236 passed (229 before, +7),service-automation365 passed.Generated by Claude Code