Skip to content
Closed
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
18 changes: 11 additions & 7 deletions gittensor/validator/issue_discovery/mirror_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,17 @@ async def run_mirror_issue_discovery(
no_issues += 1
continue

# Count this miner's currently-open issues across mirror-enabled repos
# (within the lookback window). Used as the spam-multiplier signal and
# also written to evaluation.total_open_issues so the DB row reflects
# mirror-scoped state (the legacy GraphQL global open-issue count was
# never the right signal for the gate).
open_issue_count = sum(1 for i in filtered if i.state == 'OPEN')
pending.append((evaluation, filtered, open_issue_count))
# Count this miner's total open issues across mirror-enabled repos
# without the lookback window, so old open issues correctly trigger
# the spam multiplier. The lookback filter above is for scoring;
# the spam gate needs the miner's true open-issue load.
try:
all_response = await asyncio.to_thread(client.get_miner_issues, evaluation.github_id)
all_filtered = [i for i in all_response.issues if i.repo_full_name in enabled_names]
total_open = sum(1 for i in all_filtered if i.state == 'OPEN')
except MirrorRequestError:
total_open = sum(1 for i in filtered if i.state == 'OPEN')
pending.append((evaluation, filtered, total_open))

canonical_pr_owners = _build_canonical_pr_owners(pending)
for evaluation, filtered, open_issue_count in pending:
Expand Down
20 changes: 15 additions & 5 deletions gittensor/validator/pat_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
from gittensor.constants import BASE_GITHUB_API_URL, GITHUB_HTTP_TIMEOUT_SECONDS, GRAPHQL_VIEWER_QUERY
from gittensor.synapses import PatBroadcastSynapse, PatCheckSynapse
from gittensor.validator import pat_storage
from gittensor.validator.utils.github_validation import validate_github_credentials
from gittensor.validator.utils.github_validation import (
validate_github_credentials,
validate_github_credentials_result,
)

if TYPE_CHECKING:
from neurons.validator import Validator
Expand Down Expand Up @@ -117,11 +120,18 @@ async def handle_pat_check(validator: 'Validator', synapse: PatCheckSynapse) ->
synapse.has_pat = True

# Re-validate the stored PAT
_, error = validate_github_credentials(uid, entry['pat'])
if error:
validation = validate_github_credentials_result(uid, entry['pat'], stored_github_id=entry.get('github_id'))

if validation.transient_failure:
synapse.pat_valid = None
synapse.rejection_reason = 'GitHub API temporarily unavailable; retry the check in a few minutes.'
bt.logging.warning(f'PAT check result — UID: {uid}: GitHub API transient failure')
return synapse

if validation.error:
synapse.pat_valid = False
synapse.rejection_reason = error
bt.logging.warning(f'PAT check result — UID: {uid}: validation failed: {error}')
synapse.rejection_reason = validation.error
bt.logging.warning(f'PAT check result — UID: {uid}: validation failed: {validation.error}')
return synapse

test_error = _test_pat_against_repo(entry['pat'])
Expand Down
32 changes: 29 additions & 3 deletions tests/validator/test_pat_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
handle_pat_broadcast,
handle_pat_check,
)
from gittensor.validator.utils.github_validation import GitHubCredentialValidation


def _run(coro):
Expand Down Expand Up @@ -188,7 +189,10 @@ def test_new_miner_on_uid_can_use_any_github(self, mock_validate, mock_test_quer

class TestHandlePatCheck:
@patch('gittensor.validator.pat_handler._test_pat_against_repo', return_value=None)
@patch('gittensor.validator.pat_handler.validate_github_credentials', return_value=('github_42', None))
@patch(
'gittensor.validator.pat_handler.validate_github_credentials_result',
return_value=GitHubCredentialValidation(github_id='github_42', error=None),
)
def test_valid_pat(self, mock_validate, mock_test_query, mock_validator):
pat_storage.save_pat(1, 'hotkey_1', 'ghp_test', 'github_42')

Expand All @@ -214,7 +218,10 @@ def test_stale_pat_reports_false(self, mock_validator):
assert result.pat_valid is False

@patch('gittensor.validator.pat_handler._test_pat_against_repo', return_value=None)
@patch('gittensor.validator.pat_handler.validate_github_credentials', return_value=(None, 'PAT expired'))
@patch(
'gittensor.validator.pat_handler.validate_github_credentials_result',
return_value=GitHubCredentialValidation(github_id=None, error="No Github id found for miner 1's PAT"),
)
def test_stored_but_invalid_pat(self, mock_validate, mock_test_query, mock_validator):
"""PAT is stored but fails re-validation."""
pat_storage.save_pat(1, 'hotkey_1', 'ghp_expired', 'github_42')
Expand All @@ -223,7 +230,26 @@ def test_stored_but_invalid_pat(self, mock_validate, mock_test_query, mock_valid
result = _run(handle_pat_check(mock_validator, synapse))
assert result.has_pat is True
assert result.pat_valid is False
assert 'PAT expired' in (result.rejection_reason or '')
assert 'No Github id found' in (result.rejection_reason or '')

@patch('gittensor.validator.pat_handler._test_pat_against_repo', return_value=None)
@patch(
'gittensor.validator.pat_handler.validate_github_credentials_result',
return_value=GitHubCredentialValidation(
github_id='github_42',
error='GitHub /user lookup failed transiently',
transient_failure=True,
),
)
def test_transient_failure_returns_inconclusive(self, mock_validate, mock_test_query, mock_validator):
"""Transient GitHub API failure should return pat_valid=None, not False."""
pat_storage.save_pat(1, 'hotkey_1', 'ghp_transient', 'github_42')

synapse = _make_check_synapse('hotkey_1')
result = _run(handle_pat_check(mock_validator, synapse))
assert result.has_pat is True
assert result.pat_valid is None
assert 'temporarily unavailable' in (result.rejection_reason or '')


# ---------------------------------------------------------------------------
Expand Down
Loading