Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions gittensor/validator/issue_discovery/mirror_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- solved_by_pr must be populated
- solving_pr.state == 'MERGED'
- not solving_pr.edited_after_merge
- issue.last_edited_at <= solving_pr.merged_at (anti-spec-rewrite)
- issue.state_reason == 'COMPLETED' (not NOT_PLANNED, not null)
- not issue.is_transferred
- issue.author_github_id != solving_pr.author_github_id (anti-self-issue)
Expand Down Expand Up @@ -465,6 +466,13 @@ def _classify_issue(issue: MirrorIssue) -> str:
)
return 'not-solved-closed'

if issue.last_edited_at is not None and sp.merged_at is not None and issue.last_edited_at > sp.merged_at:
bt.logging.debug(
f' issue #{issue.issue_number} ({issue.repo_full_name}): closed-not-solved '
f'(issue body/title edited after solving PR #{sp.pr_number} merge — anti-spec-rewrite gate)'
)
return 'not-solved-closed'

if not issue.author_github_id:
bt.logging.debug(f' issue #{issue.issue_number} ({issue.repo_full_name}): ignore (missing author_github_id)')
return 'ignore'
Expand Down
20 changes: 19 additions & 1 deletion tests/validator/issue_discovery/test_mirror_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def _issue_dict(
solving_pr_state: str = 'MERGED',
solving_pr_author: str = '218712309',
solving_pr_edited_after_merge: bool = False,
last_edited_at: Optional[str] = None,
repo: str = 'entrius/gittensor-ui',
) -> dict:
sp = None
Expand Down Expand Up @@ -138,7 +139,7 @@ def _issue_dict(
'created_at': '2026-04-01T00:00:00Z',
'closed_at': '2026-04-18T10:00:00Z' if state == 'CLOSED' else None,
'updated_at': '2026-04-18T10:00:00Z',
'last_edited_at': None,
'last_edited_at': last_edited_at,
'is_transferred': is_transferred,
'solved_by_pr': solved_by_pr,
'labels': [],
Expand Down Expand Up @@ -207,6 +208,23 @@ def test_solving_pr_edited_after_merge_counts_as_closed(self):
issue = MirrorIssue.from_dict(_issue_dict(solving_pr_edited_after_merge=True))
assert _classify_issue(issue) == 'not-solved-closed'

def test_issue_edited_after_solving_pr_merge_counts_as_closed(self):
# Anti-spec-rewrite: miner can't author a vague issue, then rewrite the
# body after a third party's PR merges to retroactively claim discovery
# credit for a fix they didn't anticipate.
issue = MirrorIssue.from_dict(_issue_dict(last_edited_at='2026-04-18T10:00:01Z'))
assert _classify_issue(issue) == 'not-solved-closed'

def test_issue_edited_before_solving_pr_merge_is_solved(self):
# Pre-merge edits are legitimate (sharpening the spec while the PR is
# being written) and must NOT trip the gate.
issue = MirrorIssue.from_dict(_issue_dict(last_edited_at='2026-04-17T10:00:00Z'))
assert _classify_issue(issue) == 'solved'

def test_issue_never_edited_is_solved(self):
issue = MirrorIssue.from_dict(_issue_dict(last_edited_at=None))
assert _classify_issue(issue) == 'solved'

def test_missing_author_ignored(self):
issue = MirrorIssue.from_dict(_issue_dict(author_github_id=None))
assert _classify_issue(issue) == 'ignore'
Expand Down
Loading