Description
_zero_for_duplicate_penalty() clears issue-discovery scalar fields, but it does not clear MinerEvaluation.issue_discovery_issues.
After #1235, final rewards are allocated by blend_emission_pools() from per-repo PR and issue rows. The issue allocator sums issue.discovery_earned_score directly from evaluation.issue_discovery_issues, and it does not skip evaluations with failed_reason.
That means a duplicate-GitHub miner restored from MinerEvaluationCache can be penalized, have issue_discovery_score = 0.0, but still keep stale cached issue rows that consume issue-discovery repo allocation.
In the live forward path, update_scores(..., blacklisted_uids=...) later zeros penalized UIDs, but the repo slice has already been divided using their stale issue rows. This can starve legitimate issue discoverers or prevent the slice from recycling correctly.
Affected Code
gittensor/validator/oss_contributions/inspections.py
_zero_for_duplicate_penalty() clears PR lists and issue-discovery counters, but not issue_discovery_issues.
gittensor/validator/emission_allocation.py
_collect_repo_issue_discovery_scores() reads evaluation.issue_discovery_issues directly.
Steps To Reproduce
Run this on current test:
.venv/bin/python - <<'PY'
from gittensor.classes import Issue, MinerEvaluation
from gittensor.validator.oss_contributions.inspections import detect_and_penalize_miners_sharing_github
from gittensor.validator.emission_allocation import blend_emission_pools
from gittensor.validator.utils.load_weights import RepositoryConfig
evals = {}
for uid in (1, 2):
ev = MinerEvaluation(uid=uid, hotkey=f"hk{uid}", github_id="shared")
issue = Issue(number=uid, pr_number=100 + uid, repository_full_name="r/issue", title="cached")
issue.discovery_earned_score = 10.0
ev.issue_discovery_issues = [issue]
ev.issue_discovery_score = 10.0
evals[uid] = ev
penalized = detect_and_penalize_miners_sharing_github(evals)
print("penalized:", sorted(penalized))
print("scalar_scores:", [evals[u].issue_discovery_score for u in (1, 2)])
print("issue_rows_left:", [len(evals[u].issue_discovery_issues) for u in (1, 2)])
repos = {"r/issue": RepositoryConfig(emission_share=1.0, issue_discovery_share=1.0)}
uids = {0, 1, 2, 111}
rewards = blend_emission_pools(evals, repos, uids)
print(dict(zip(sorted(uids), map(float, rewards))))
PY
Observed output:
penalized: [1, 2]
scalar_scores: [0.0, 0.0]
issue_rows_left: [1, 1]
{0: 0.0, 1: 0.45, 2: 0.45, 111: 0.1}
Expected Behavior
Duplicate-penalized miners should not contribute any PR-side or issue-discovery rows to repo allocation.
After penalty:
issue_discovery_score should be 0.0
issue_discovery_issues should be empty
_collect_repo_issue_discovery_scores() should not see score-bearing rows from failed/penalized evaluations
Actual Behavior
The penalty clears issue_discovery_score and counters, but stale issue_discovery_issues remain populated. blend_emission_pools() still allocates issue-discovery repo share to those rows.
Suggested Fix
At minimum, clear the issue row list in _zero_for_duplicate_penalty():
eval_.issue_discovery_issues = []
A more defensive follow-up would make the emission collectors skip evaluations with failed_reason is not None.
Acceptance Criteria
- Add a regression test where duplicate-GitHub evaluations have populated
issue_discovery_issues.
- After
detect_and_penalize_miners_sharing_github(), assert the issue row list is empty.
- Add or extend an allocation test proving penalized/failed evaluations do not consume issue-discovery repo slices.
Related / Not Duplicate
Description
_zero_for_duplicate_penalty()clears issue-discovery scalar fields, but it does not clearMinerEvaluation.issue_discovery_issues.After #1235, final rewards are allocated by
blend_emission_pools()from per-repo PR and issue rows. The issue allocator sumsissue.discovery_earned_scoredirectly fromevaluation.issue_discovery_issues, and it does not skip evaluations withfailed_reason.That means a duplicate-GitHub miner restored from
MinerEvaluationCachecan be penalized, haveissue_discovery_score = 0.0, but still keep stale cached issue rows that consume issue-discovery repo allocation.In the live forward path,
update_scores(..., blacklisted_uids=...)later zeros penalized UIDs, but the repo slice has already been divided using their stale issue rows. This can starve legitimate issue discoverers or prevent the slice from recycling correctly.Affected Code
gittensor/validator/oss_contributions/inspections.py_zero_for_duplicate_penalty()clears PR lists and issue-discovery counters, but notissue_discovery_issues.gittensor/validator/emission_allocation.py_collect_repo_issue_discovery_scores()readsevaluation.issue_discovery_issuesdirectly.Steps To Reproduce
Run this on current
test:Observed output:
Expected Behavior
Duplicate-penalized miners should not contribute any PR-side or issue-discovery rows to repo allocation.
After penalty:
issue_discovery_scoreshould be0.0issue_discovery_issuesshould be empty_collect_repo_issue_discovery_scores()should not see score-bearing rows from failed/penalized evaluationsActual Behavior
The penalty clears
issue_discovery_scoreand counters, but staleissue_discovery_issuesremain populated.blend_emission_pools()still allocates issue-discovery repo share to those rows.Suggested Fix
At minimum, clear the issue row list in
_zero_for_duplicate_penalty():A more defensive follow-up would make the emission collectors skip evaluations with
failed_reason is not None.Acceptance Criteria
issue_discovery_issues.detect_and_penalize_miners_sharing_github(), assert the issue row list is empty.Related / Not Duplicate
stale_closed_pull_requestspopulated, letting penalized miners continue writing stale PR rows #1006 covered the same field-list risk forstale_closed_pull_requests, a storage-only PR bucket. This issue affects score-bearing issue-discovery allocation after feat(rewards): allocate emissions by repository share #1235.