fix(review): persist linked-issue hard-rule violations past the grace window#4032
Conversation
… window resolveLinkedIssueHardRule is fully re-evaluated from scratch every pass -- it re-parses linked issues from the PR's CURRENT body and reads each linked issue's CURRENT live state, with no memory of a prior pass's finding. During the flag-then-close verification window (settings.linkedIssueHardRules.closeDelaySeconds), that statelessness lets a confirmed violation dodge the close two ways: editing the PR body to strip the closing reference so the next pass sees zero linked issues, or the linked issue's live state changing (e.g. unassigned) between the violating pass and the verification pass. Add linkedIssueHardRuleViolatedAt/linkedIssueHardRuleViolationReason columns (mirroring draftConversionCount's never-resets discipline and mergeBlockedReason's pairing with mergeBlockedSha) so a PR that ever confirms a violation stays flagged for its lifetime, merged with the live re-parse result rather than replacing it.
|
Caution 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 🛑 Gittensory review result - fixes requiredReview updated: 2026-07-07 11:39:27 UTC
🛑 Suggested Action - Manual Review
Why this is blocked
CI checks failing
Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
❌ 51 Tests Failed:
View the top 3 failed test(s) by shortest run time
View the full list of 48 ❄️ flaky test(s)
To view more test analytics, go to the Test Analytics Dashboard |
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
0119 was claimed by #4025 (ai_slop_cache), which merged first -- renumbered this migration to the next free number.
0119 was claimed by another merged PR (ai_slop_cache), and 0120 by #4032, while this one was in flight. Renumbered to the next free slot on top of the current branch tip.
0119 was claimed by another merged PR (ai_slop_cache), and 0120 by #4032, while this one was in flight. Renumbered to the next free slot on top of the current branch tip.
* wip(review): merge-train settings plumbing checkpoint (schema+types+migration, executor wiring pending)
* feat(review): merge-train FIFO gate for out-of-order merges
A PR currently merges the instant its OWN gate clears, with zero
awareness of an older sibling PR still open in the same repo -- a
live production query proved this causes hundreds of out-of-order
merges per repo (505/708/1364 across the three gated repos), and the
conflicts that follow.
Adds a new settings.mergeTrainMode ("off" | "audit" | "enforce",
default "off") gate: "audit" logs what would be held without holding
anything; "enforce" actually defers a merge behind a still-viable
older sibling, bounded by a 24h staleness cap so one stuck PR can
never wedge the repo's newer ones forever. A git-conflicted sibling
never blocks -- it isn't about to merge, it's stuck.
The decision itself (src/review/merge-train.ts) is a pure function
reusing data already fetched on every merge pass (listOtherOpenPullRequests,
already ordered ascending by PR number) -- no new query, no new
persisted state beyond the one settings column. Wired into
executeAgentMaintenanceActions right before the actual merge mutation,
threaded through the two call sites that can plan a merge action.
* fix(queue): renumber merge-train migration 0119 -> 0121 (collision)
0119 was claimed by another merged PR (ai_slop_cache), and 0120 by
#4032, while this one was in flight. Renumbered to the next free
slot on top of the current branch tip.
Summary
resolveLinkedIssueHardRuleis fully re-evaluated from scratch every pass: it re-parses linked issues from the PR's CURRENT body and reads each linked issue's CURRENT live state, with no memory of a prior pass's finding.settings.linkedIssueHardRules.closeDelaySeconds), that statelessness lets a confirmed violation dodge the close two ways: (1) editing the PR body during the grace window to strip the closing reference, so the next pass sees zero linked issues; (2) the linked issue's live state changing between the violating pass and the verification pass (e.g. the assignee is removed), so the same issue number re-evaluates clean.linkedIssueHardRuleViolatedAt/linkedIssueHardRuleViolationReasoncolumns onpull_requests(mirroringdraftConversionCount's never-resets discipline andmergeBlockedReason's pairing withmergeBlockedSha) — set once on the FIRST confirmed violation, never cleared, not scoped to head SHA.mergeLinkedIssueHardRuleWithPersistedViolationmerges this persisted memory with the live re-parse: a live violation always wins for the reason text (freshest), but persisted memory forcesviolated: trueeven when the live pass no longer reproduces it.Scope
wantedPaths(src/,migrations/,test/)Validation
npm run typecheck— cleannpx vitest run test/unit/linked-issue-hard-rules.test.ts test/unit/queue.test.ts— all passingSafety
site/,CNAME,lovable/, or the changelog