feat(issues): count an open pull request as an in_review review path (PEN-2853) - #1851
Open
allyblockcast[bot] wants to merge 2 commits into
Open
allyblockcast[bot] wants to merge 2 commits into
allyblockcast[bot] wants to merge 2 commits into
Conversation
…(PEN-2853) An agent-authored move to `in_review` had five valid review paths, and none of them was an external event wake or an open pull request. For the commonest shape an agent finishes in -- a green PR awaiting a human merge press -- no path described the row honestly: nobody to ask a question of, no board decision to link, merge is a press rather than a review assignment, and no execution policy. That left `scheduled_issue_monitor` as the only unilaterally satisfiable remedy, so the 422's practical advice was to arm a poll against a gate no poll can move. The agent was pushed either to game the check or to leave the row `in_progress` -- which reads as "actively being worked" when the truth is "waiting on a human", and, being uncounted, left the row seizable by the strandedness sweep (PEN-2370). Adds a sixth path, `open_pull_request_work_product`, which delegates to the sweep's own predicate rather than restating it. That delegation is the point: this row was filed because two independently-written predicates disagreed about the same path, so a copy here would have reproduced the divergence on a third. `hasOpenPullRequestWakePath` therefore moves out of `recovery/service.ts` into `services/open-pull-request-attendance.ts`, and both callers consult it. The validator inherits the sweep's grace and provenance filters rather than relaxing them. A point-in-time reading is tempting -- a 10-day-old open PR is arguably "in review" at the instant of the write -- but it would recreate Finding 2's defect in a new column: admitted by the write side, discounted by the read side, seizable the moment it lands. The invariant both callers preserve is that everything the validator admits, the sweep counts. Not placed in `pull-request-work-products.ts` despite owning the constants it reads: that module documents itself as DB- and network-free so the webhook mapping stays unit-testable without Postgres. Evidence: the agreement test drives the real route against real Postgres and takes its expectation from the shared predicate evaluated on the same row, so it fails in either direction. Refusals must be 422 `invalid_issue_disposition` -- without that, a 409 ownership conflict compares equal to "refused" and four of five fixtures pass while proving nothing. Mutation-checked. Disabling only the new disjunct turns exactly the two new tests red -- `issue-recovery-actions` 1 failed / 5 passed in that describe, and `issue-execution-policy-routes` 1 failed / 46 passed -- and nothing else moves. Restored: 191/191 and 47/47. `tsc --noEmit` exit 0. Finding 2 of PEN-2853 shipped separately in #1588; this is Finding 1. Signed-off-by: Cto <cto@paperclip.blockcast.net>
Author
1 similar comment
Author
Author
|
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 |
… (PEN-2853) PR #1834 (PEN-3198) extends the same PEN-2791 ruling to a third gate, the issue-graph liveness classifier, and to do so defines its own `openPullRequestWakePathConditions` as a LOCAL function inside `recovery/service.ts` -- which `routes/` cannot import. Left alone, the two PRs would land two independent "single definitions" of one predicate, on the row filed because that exact thing happened once already. Splits the conditions out under the same name #1834 chose and exports them, so that PR's rebase is an import rather than a judgement call. No behaviour change: 191/191 and 47/47 still pass, `tsc --noEmit` exit 0. Signed-off-by: Cto <cto@paperclip.blockcast.net>
Author
|
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 |
Author
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. The configured lens binaries were unavailable in this runtime; their review prompts were applied directly to the exact PR diff.
Reviewed head: f2ce407
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- The shared attendance predicate keeps the write-side
in_reviewvalidator and the recovery sweep aligned instead of duplicating SQL logic. - The query preserves the existing status, webhook provenance, source-trust, and freshness constraints, avoiding a broad admission of arbitrary or abandoned PR links.
- The agreement test exercises both admission and refusal paths and explicitly guards against a route-level
409masking a validator result.
Recommended Action
- No Critical or Important changes requested. CI is still running; merge after the required checks pass.
Open
13 tasks
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.
Thinking Path
Linked Issues or Issue Description
Related open PRs found in the dedup search — please read before merging:
assertAgentInReviewReviewPathandissue-execution-policy-routes.test.ts. Textual conflict only; the two changes are independent in intent.issue-recovery-actions.test.tsandrecovery/service.ts. Textual conflict only.What Changed
hasOpenPullRequestWakePathout ofrecovery/service.tsinto a newservices/open-pull-request-attendance.ts, so it has one home and more than one caller. The sweep now imports it; its logic is unchanged.validReviewPath,open_pull_request_work_product, toassertAgentInReviewReviewPath, delegating to that shared predicate.validReviewPathsarray andINVALID_AGENT_IN_REVIEW_DISPOSITION_MESSAGE, which now tells a PR-gated caller to open the PR rather than arm a monitor.openPullRequestWakePathConditionsunder the same name fix(recovery): count an open pull request as an in-review action path (PEN-3198) #1834 chose, so that PR's rebase is an import rather than a second definition.The validator inherits the sweep's grace and provenance filters rather than relaxing them. A point-in-time reading is tempting — a 10-day-old open PR is arguably "in review" at the instant of the write — but it would recreate Finding 2's defect in a new column: admitted by the write side, discounted by the read side, seizable the moment it lands. The invariant both callers preserve is everything the validator admits, the sweep counts. The asymmetry is safe in one direction only.
Deliberately not placed in
pull-request-work-products.tsdespite that module owning the constants it reads: it documents itself as DB- and network-free so the webhook mapping stays unit-testable without Postgres.Verification
Agreement test, on a real row. Driven through the real route against real Postgres, because the filters that matter — terminal status, webhook provenance, the
updatedAtgrace — live in SQL; a mock returning rows regardless of the WHERE clause would pass just as happily with every filter deleted. The expectation is not hand-written per fixture: it ishasOpenPullRequestWakePathitself, evaluated on the same row the route just saw, so it fails in either direction. Fixtures: no PR · open webhook-written PR · merged PR · hand-created row · open PR aged past the grace.409before the validator ever runs — and409 !== 200compares equal to "refused", so four of five fixtures passed while proving nothing. The test now asserts refusals are specifically422 invalid_issue_disposition, which is what stops a non-validator status masquerading as a verdict.Mutation check. Disabling only the new disjunct:
issue-recovery-actions(open-PR describe)issue-execution-policy-routesExactly the two new tests go red and nothing else moves, so they are pinned to this disjunct rather than riding on a neighbour.
Risks
The real one is #1834, and it needs a human decision, not a rebase-and-hope. That PR adds open-PR attendance to a third predicate and, to do it, defines its own
openPullRequestWakePathConditionsas a local function insiderecovery/service.ts— whichroutes/cannot import. This PR instead moves the predicate out to a shared module. Both edit the same lines.Left alone the two would land two independent "single definitions" of one predicate — on the row filed because exactly that happened once already. To make converging cheap rather than a judgement call, this PR exports the conditions under the name #1834 already uses, so whichever merges second replaces its local definition with an import. Recommend merging the two deliberately in sequence, not concurrently.
Otherwise low risk, and the failure mode is conservative in the right direction:
in_reviewbefore is blocked now.recovery/service.tsbehaviour is unchanged; the predicate moved file, its logic did not (191/191).Model Used
Claude Opus 4.5 (
claude-opus-4-5, 1M context), extended thinking, via Claude Code with tool use and code execution.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template422message and the module docblocks are the user-facing documentation of this ruleverifystill running at time of writing🤖 Generated with Claude Code