Skip to content

orb(gate): overlap-preserving claim timestamps let an old PR backdate a claim on any issue and steal the duplicate-winner slot #9160

Description

@JSONbored

Problem

linkedIssueClaimedAt is a single PR-level timestamp, and resolveLinkedIssueClaimedAt preserves it
whenever the new linked-issue set overlaps the old one (src/db/repositories.ts:535-562):

if (existing.linkedIssuesJson === linkedIssuesJson ||
    linkedIssueSetsOverlap(parseLinkedIssuesJson(existing.linkedIssuesJson), linkedIssues))
  return existing.linkedIssueClaimedAt ?? observedLinkedIssueClaimedAt;

So a PR that claimed #1 on day 1 and later edits its body to Fixes #1, Fixes #7 keeps the day-1
timestamp for #7 as well
.

packages/loopover-engine/src/duplicate-winner.ts:14-18 documents that creation time is deliberately
excluded from the election, and why:

contributors can edit an old placeholder PR to add a linked issue later, so creation time would let
backdated claims steal duplicate-winner credit

The #linked-issue-claim-overlap-preserve fix reintroduced exactly that backdating by a different route.
Its own reasoning is sound for the case it addresses (a pure addition shouldn't reset issue #1's clock) —
the flaw is that one timestamp cannot serve two issues.

Trigger

An attacker keeps a long-lived PR linking a throwaway issue. A victim opens a PR claiming valuable issue
#7. The attacker edits their body to add Fixes #7 — and can copy the victim's diff. The attacker's
claim time is now weeks earlier, so isDuplicateClusterWinnerByClaim
(duplicate-winner.ts:55-63) elects them; the victim gets duplicate of open PR #<attacker> and is
auto-closed. A follow-up edit dropping #1 still overlaps {1,7}, so the backdate persists.

Impact

Reward theft plus one-shot destruction of the genuine claimant's PR — the copycat-farming economics of
#9033, reached through the claim ledger rather than content similarity. Pairs directly with #9129 (the
duplicate rule as an offensive close primitive): that issue is how you fire the weapon, this is how you
make sure you win the exchange.

Impact is gated on LOOPOVER_DUPLICATE_WINNER, which defaults off — confirm the live setting before
prioritising.

Requirements

  • Store the claim time per (PR, issue) rather than per PR, so adding a new issue starts that issue's
    clock fresh while existing claims keep theirs. This resolves the original
    #linked-issue-claim-overlap-preserve bug and this one simultaneously; the current single-column shape
    cannot satisfy both.
  • Contiguous migrations/NNNN_*.sql for the new table/column.
  • Add an adversarial regression test: adding an issue to an old PR must not backdate the new claim.

Test Coverage Requirements

99%+ patch coverage, branch-counted; the overlap-preserve case and the new-issue-added case both asserted.

Links & Resources

maintainer-only — reward-integrity and close authority.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions