fix(review): default cross-issue copycat/reward-farming dedupe on for reward-eligible repos#9226
Merged
Merged
Conversation
…9033) copycatGateMode's absent-value fallback was a flat "off" for every repo (config-as-code only, no DB column), so the deterministic containment engine never ran unless a repo explicitly opted in via .loopover.yml -- a direct economic leak for a reward-eligible repo, since colluding accounts could file near-identical fixes and both merge undetected. resolveEffectiveCopycatGateMode resolves an unset mode to "warn" for a subnet-registered (reward-eligible) repo instead, so the engine runs and persists copycatScore/copycatMatchedPullNumber -- the signal the duplicate-cluster election needs. An explicit .loopover.yml value, including an explicit "off", always wins over this default.
…election (#9033) The duplicate-winner election (queue/duplicate-detection.ts) keyed entirely on shared linkedIssues overlap, so two PRs citing different issues -- one a near-identical copy of the other -- were never even considered duplicate-cluster candidates, letting both merge and both earn rewards independently. resolveCopycatDuplicateSibling (src/signals/copycat-duplicate.ts) bridges a PR's already-persisted copycat containment match into linkedIssueDuplicatePullRequestRecordsForGate and reconcileLiveDuplicateSiblings, so a cross-issue content match above the configured threshold now feeds the SAME winner-election, close- count, and live-staleness-reconciliation machinery a same-linked-issue duplicate already does. Scoped to still-open siblings only -- a match against an already-merged PR is left to copycatGateMode's own single-PR warn/label/block actuation. Every new parameter is optional and defaults to "never contributes a copycat sibling", so no existing caller's behavior changes.
… overlap (#9033) The duplicate_pr_risk gate finding -- the mechanism that actually holds or blocks a PR as a duplicate -- keyed strictly on shared linkedIssues overlap, so a cross-issue copycat containment match never surfaced here even after the duplicate-cluster election learned about it: the election could name a winner, but the loser's gate never actually failed on it. addPullRequestFindings now folds a PR's own persisted copycat containment match into the same overlap set a shared linked issue produces, treating it as corroborated by construction (a threshold- cleared content match is stronger evidence than a bare changed-file overlap). This is what makes the loser in a cross-issue reward-farming attempt actually get held/closed instead of merging independently.
…art by overlap (#9033) MAX_COPYCAT_CANDIDATES (25) capped how many prior-art candidates got their full patch content fetched and scored -- on an active repo, a copycat's actual match could sit past that cutoff and never get compared at all. Raised to 50 (still a bounded, cheap, single-repo-scoped set of D1 reads, not external API calls). The recently-merged phase also now ranks its path-overlapping candidates by OVERLAP COUNT descending before spending its share of the budget, rather than merely the most recently merged ones that happen to overlap at all -- a stronger prior signal of a genuine match, at no extra fetch cost since changedFiles is already loaded.
…ssion (#9033) Exercises the full confirmed scenario from #9033: two PRs on a reward- eligible repo, different linked issues, near-identical added code. Covers reward-eligible settings resolution, the duplicate-cluster helpers, the gate finding/conclusion, and the actual maintenance disposition -- proving the later cross-issue copycat PR is held for manual review (never merges independently) while the earlier claimant merges untouched on its own merits. Also documents an important existing interaction: #9129 already downgrades any gate failure caused solely by duplicate_pr_risk (even a corroborated one) to a neutral hold rather than an outright close, so the cross-issue copycat match now goes through that identical, precision-safe mechanism instead of bypassing it.
…te-decision twin (#9033) src/rules/advisory.ts's evaluateGateCheckCore gained a host-only cross-issue copycat corroboration branch (the duplicate-cluster election fix). It intentionally does not mirror into packages/loopover-engine/src/advisory/gate-advisory.ts -- the engine twin is deliberately kept slim (no signals/engine, no PullRequestRecord.copycatScore) so the miner/mcp CLI packages never drag that dependency graph in. Bumps the engine package version (the guard's own documented escape hatch for a one-sided fix, same shape as the precedent manual bump in 6aacf08), syncing package-lock.json, the miner's expected-engine.version pin, and the release-please manifest alongside it.
Contributor
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Contributor
Logic backtestReplayed 0 historical case(s) for Backtest comparison:
|
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
loopover-ui | 03ef003 | Commit Preview URL Branch Preview URL |
Jul 27 2026, 08:04 AM |
Bundle ReportBundle size has no change ✅ |
❌ 4 Tests Failed:
View the top 3 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
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.
Summary
Two colluding accounts (or one attacker with two identities) could file near-identical fixes under different linked issues and both merge — double reward for one piece of work.
Test plan
npx tsc --noEmit -p tsconfig.json --incremental false— cleanmain(picked up fix(orb): installation/permission/config integrity — suspension backfill, broker identity, numeric env validation, migration content hash #9222, feat(attest): pluggable attester seam with a deterministic sample implementation #9220 — no conflicts, re-verified)Closes #9033