From 137ba760368ce46d22c0802c94cde2bb35f60dd4 Mon Sep 17 00:00:00 2001 From: alpurkan17 Date: Tue, 12 May 2026 18:08:28 +0000 Subject: [PATCH 1/3] fix: reject GraphQL viewer:null in gitt miner post local PAT preflight (#1195) --- gittensor/cli/miner_commands/post.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gittensor/cli/miner_commands/post.py b/gittensor/cli/miner_commands/post.py index 40551ded..7ab1fc16 100644 --- a/gittensor/cli/miner_commands/post.py +++ b/gittensor/cli/miner_commands/post.py @@ -220,6 +220,13 @@ def _validate_pat_locally(pat: str) -> str | None: ) return None + gql_data = gql_resp.json() + if (gql_data.get('data') or {}).get('viewer') is None: + err_console.print( + '[red]PAT lacks GraphQL API access. Fine-grained PATs need "Public Repositories (read-only)" permission.[/red]' + ) + return None + return login except requests.RequestException: return None From 49664b3706f02901856bed45cd27195b796cad46 Mon Sep 17 00:00:00 2001 From: Ander <61125407+anderdc@users.noreply.github.com> Date: Thu, 14 May 2026 22:24:42 -0500 Subject: [PATCH 2/3] fix(validator): bound tree-sitter parse to prevent scoring-round DoS (#1276) Co-authored-by: anderdc --- gittensor/constants.py | 5 +++++ gittensor/validator/oss_contributions/mirror/scoring.py | 7 ++++++- gittensor/validator/utils/tree_sitter_scoring.py | 3 +++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/gittensor/constants.py b/gittensor/constants.py index 5edbf679..0297ccd2 100644 --- a/gittensor/constants.py +++ b/gittensor/constants.py @@ -100,6 +100,11 @@ TIME_DECAY_SIGMOID_STEEPNESS_SCALAR = 0.4 TIME_DECAY_MIN_MULTIPLIER = 0.05 # 5% of score will retain through lookback window +# Per-parse CPU budget for tree-sitter. The parser polls this flag in its +# error-recovery loops; without it, adversarial inputs can spin forever in C +# while holding the GIL. 2s is well above the millisecond cost of real files. +TREE_SITTER_PARSE_TIMEOUT_MICROS = 2_000_000 + # comment nodes for token scoring COMMENT_NODE_TYPES = frozenset( { diff --git a/gittensor/validator/oss_contributions/mirror/scoring.py b/gittensor/validator/oss_contributions/mirror/scoring.py index 2ce426ef..97c7910f 100644 --- a/gittensor/validator/oss_contributions/mirror/scoring.py +++ b/gittensor/validator/oss_contributions/mirror/scoring.py @@ -97,7 +97,12 @@ async def score_miner_prs( bt.logging.info( f'\n[{i}/{len(scored_prs)}] {label} PR #{scored.pr.pr_number} in {scored.pr.repo_full_name}' ) - await score_pr(scored, eval_, master_repositories, programming_languages, token_config, client) + try: + await score_pr(scored, eval_, master_repositories, programming_languages, token_config, client) + except Exception as e: + bt.logging.warning( + f'UID {eval_.uid}: scoring failed for PR #{scored.pr.pr_number} in {scored.pr.repo_full_name}: {e}' + ) # ============================================================================ diff --git a/gittensor/validator/utils/tree_sitter_scoring.py b/gittensor/validator/utils/tree_sitter_scoring.py index bf36aa75..b58e29aa 100644 --- a/gittensor/validator/utils/tree_sitter_scoring.py +++ b/gittensor/validator/utils/tree_sitter_scoring.py @@ -21,6 +21,7 @@ MAX_LINES_SCORED_FOR_NON_CODE_EXT, NON_CODE_EXTENSIONS, TEST_FILE_CONTRIBUTION_WEIGHT, + TREE_SITTER_PARSE_TIMEOUT_MICROS, ) from gittensor.utils.github_api_tools import FileContentPair from gittensor.utils.logging import log_scoring_results @@ -51,6 +52,8 @@ def get_parser(language: str) -> Optional[Parser]: from tree_sitter_language_pack import get_parser as get_ts_parser parser = get_ts_parser(language) # type: ignore[arg-type] + # Bound the C-level parse so adversarial inputs cannot hang the round. + parser.timeout_micros = TREE_SITTER_PARSE_TIMEOUT_MICROS _parser_cache[language] = parser return parser except Exception as e: From bbe50d5b54cc6318ff9764aea39402f179a8d92d Mon Sep 17 00:00:00 2001 From: Ander <61125407+anderdc@users.noreply.github.com> Date: Fri, 15 May 2026 10:39:21 -0500 Subject: [PATCH 3/3] chore(weights): retune master shares, add genie/jvm/hub, drop inactive_at (#1285) Co-authored-by: anderdc --- gittensor/classes.py | 12 +- gittensor/constants.py | 8 - .../oss_contributions/mirror/adapters.py | 2 - .../oss_contributions/mirror/load.py | 13 - .../oss_contributions/mirror/scored_pr.py | 16 +- .../oss_contributions/mirror/scoring.py | 4 +- .../validator/oss_contributions/reward.py | 2 +- .../validator/oss_contributions/scoring.py | 85 +------ gittensor/validator/storage/queries.py | 6 +- gittensor/validator/storage/repository.py | 2 - gittensor/validator/utils/load_weights.py | 3 - .../weights/master_repositories.json | 24 +- .../oss_contributions/mirror/test_adapters.py | 5 - .../oss_contributions/mirror/test_load.py | 26 +- .../oss_contributions/mirror/test_pioneer.py | 132 ---------- .../mirror/test_scored_pr.py | 24 -- tests/validator/test_load_weights.py | 46 ---- tests/validator/test_pioneer_dividend.py | 231 ------------------ 18 files changed, 31 insertions(+), 610 deletions(-) delete mode 100644 tests/validator/oss_contributions/mirror/test_pioneer.py delete mode 100644 tests/validator/test_pioneer_dividend.py diff --git a/gittensor/classes.py b/gittensor/classes.py index f13f24f5..3942da42 100644 --- a/gittensor/classes.py +++ b/gittensor/classes.py @@ -17,7 +17,6 @@ from gittensor.constants import ( EXTENSIONLESS_FILE_EXTENSIONS, MAX_CODE_DENSITY_MULTIPLIER, - MIN_TOKEN_SCORE_FOR_BASE_SCORE, ) @@ -184,8 +183,6 @@ class PullRequest: base_score: float = 0.0 issue_multiplier: float = 1.0 open_pr_spam_multiplier: float = 1.0 - pioneer_dividend: float = 0.0 # Additive bonus for pioneering a repo - pioneer_rank: int = 0 # 0 = not eligible, 1 = pioneer, 2+ = follower position time_decay_multiplier: float = 1.0 credibility_multiplier: float = 1.0 review_quality_multiplier: float = 1.0 # Penalty for CHANGES_REQUESTED reviews from maintainers @@ -222,15 +219,8 @@ def set_file_changes(self, file_changes: List[FileChange]) -> None: """Set the file changes for this pull request""" self.file_changes = file_changes - def is_pioneer_eligible(self) -> bool: - """Check if this PR qualifies for pioneer consideration. - - A PR is eligible if it is merged and meets the minimum token score quality gate. - """ - return self.merged_at is not None and self.token_score >= MIN_TOKEN_SCORE_FOR_BASE_SCORE - def calculate_final_earned_score(self) -> float: - """Combine base score with all multipliers. Pioneer dividend is added separately after.""" + """Combine base score with all multipliers.""" multipliers = { 'issue': self.issue_multiplier, 'label': self.label_multiplier, diff --git a/gittensor/constants.py b/gittensor/constants.py index 0297ccd2..84fa8dbd 100644 --- a/gittensor/constants.py +++ b/gittensor/constants.py @@ -83,14 +83,6 @@ # Boosts MAX_CODE_DENSITY_MULTIPLIER = 1.15 -# Pioneer dividend — rewards the first quality contributor to each repository -# Rates applied per follower position (1st follower pays most, diminishing after) -# Dividend capped at PIONEER_DIVIDEND_MAX_RATIO × pioneer's own earned_score -PIONEER_DIVIDEND_RATE_1ST = 0.30 # 1st follower: 30% of their earned_score -PIONEER_DIVIDEND_RATE_2ND = 0.20 # 2nd follower: 20% of their earned_score -PIONEER_DIVIDEND_RATE_REST = 0.10 # 3rd+ followers: 10% of their earned_score -PIONEER_DIVIDEND_MAX_RATIO = 1.0 # Cap dividend at 1× pioneer's own earned_score (max 2× total) - # Issue boosts MAX_ISSUE_CLOSE_WINDOW_DAYS = 1 diff --git a/gittensor/validator/oss_contributions/mirror/adapters.py b/gittensor/validator/oss_contributions/mirror/adapters.py index 21802b6d..98df9a81 100644 --- a/gittensor/validator/oss_contributions/mirror/adapters.py +++ b/gittensor/validator/oss_contributions/mirror/adapters.py @@ -119,8 +119,6 @@ def mirror_scored_pr_to_legacy_pull_request( base_score=scored.base_score, issue_multiplier=scored.issue_multiplier, open_pr_spam_multiplier=scored.open_pr_spam_multiplier, - pioneer_dividend=scored.pioneer_dividend, - pioneer_rank=scored.pioneer_rank, time_decay_multiplier=scored.time_decay_multiplier, credibility_multiplier=scored.credibility_multiplier, review_quality_multiplier=scored.review_quality_multiplier, diff --git a/gittensor/validator/oss_contributions/mirror/load.py b/gittensor/validator/oss_contributions/mirror/load.py index 6a127b1c..fb891e79 100644 --- a/gittensor/validator/oss_contributions/mirror/load.py +++ b/gittensor/validator/oss_contributions/mirror/load.py @@ -6,7 +6,6 @@ Filtering applied at load time: - Repo not in master_repositories: dropped (mirror returns all tracked repos). -- PR created after repo became inactive: dropped. - PR author is a maintainer (OWNER/MEMBER/COLLABORATOR): silently dropped. - CLOSED PRs created before the lookback window: dropped — closing an old PR shouldn't trigger a fresh credibility penalty. @@ -27,7 +26,6 @@ from gittensor.utils.mirror.models import MirrorPullRequest from gittensor.validator.oss_contributions.mirror.scored_pr import ScoredPR from gittensor.validator.oss_contributions.mirror.scoring import _should_skip_merged_mirror_pr -from gittensor.validator.utils.datetime_utils import parse_github_iso_to_utc from gittensor.validator.utils.load_weights import RepositoryConfig @@ -94,17 +92,6 @@ def _maybe_add_pr( bt.logging.debug(f'Skipping PR #{pr.pr_number} in {pr.repo_full_name} - not in master_repositories') return - # Skip PR if it was created after the repo became inactive - if repo_config.inactive_at is not None: - inactive_dt = parse_github_iso_to_utc(repo_config.inactive_at) - if pr.created_at >= inactive_dt: - bt.logging.info( - f'Skipping PR #{pr.pr_number} in {pr.repo_full_name} - ' - f'PR was created after repo became inactive ' - f'(created: {pr.created_at.isoformat()}, inactive: {inactive_dt.isoformat()})' - ) - return - # Silent maintainer skip — logging every maintainer-merged PR would dominate # the skip-reason log. if not os.environ.get('DEV_MODE') and pr.author_association in MAINTAINER_ASSOCIATIONS: diff --git a/gittensor/validator/oss_contributions/mirror/scored_pr.py b/gittensor/validator/oss_contributions/mirror/scored_pr.py index 63b0ae7d..51fec15d 100644 --- a/gittensor/validator/oss_contributions/mirror/scored_pr.py +++ b/gittensor/validator/oss_contributions/mirror/scored_pr.py @@ -7,7 +7,7 @@ The scoring fields and the ``number`` / ``repository_full_name`` / ``merged_at`` aliases below are shaped to match ``PullRequest`` (the storage-layer type) so -shared scoring helpers (``calculate_final_earned_score``, ``is_pioneer_eligible``, +shared scoring helpers (``calculate_final_earned_score``, ``calculate_open_pr_collateral_score``) work on either type unchanged. """ @@ -16,7 +16,6 @@ from typing import List, Optional from gittensor.classes import _apply_score_multipliers -from gittensor.constants import MIN_TOKEN_SCORE_FOR_BASE_SCORE from gittensor.utils.mirror.models import MirrorFile, MirrorPullRequest @@ -35,10 +34,6 @@ class ScoredPR: label_multiplier: float = 1.0 label: Optional[str] = None - # Pioneer attribution (per-repo, populated post per-PR scoring) - pioneer_dividend: float = 0.0 - pioneer_rank: int = 0 # 0 = not eligible, 1 = pioneer, 2+ = follower position - # Score outputs base_score: float = 0.0 earned_score: float = 0.0 @@ -76,16 +71,11 @@ def changes_requested_count(self) -> int: @property def merged_at(self) -> Optional[datetime]: - """Alias for ``self.pr.merged_at`` — matches the ``PullRequest`` field - name so the pioneer-dividend walk treats both types identically.""" + """Alias for ``self.pr.merged_at`` — matches the ``PullRequest`` field name.""" return self.pr.merged_at - def is_pioneer_eligible(self) -> bool: - """Pioneer-eligible iff merged AND meets the minimum token-score gate.""" - return self.pr.merged_at is not None and self.token_score >= MIN_TOKEN_SCORE_FOR_BASE_SCORE - def calculate_final_earned_score(self) -> float: - """Combine base score with all multipliers. Pioneer dividend is added separately after.""" + """Combine base score with all multipliers.""" multipliers = { 'issue': self.issue_multiplier, 'label': self.label_multiplier, diff --git a/gittensor/validator/oss_contributions/mirror/scoring.py b/gittensor/validator/oss_contributions/mirror/scoring.py index 97c7910f..9e4c97b2 100644 --- a/gittensor/validator/oss_contributions/mirror/scoring.py +++ b/gittensor/validator/oss_contributions/mirror/scoring.py @@ -10,7 +10,7 @@ Cross-path concerns handled by ``finalize_miner_scores`` in ``gittensor.validator.oss_contributions.scoring`` (walks ``merged_prs``): -spam_multiplier, credibility_multiplier, pioneer dividends, final earned_score +spam_multiplier, credibility_multiplier, final earned_score composition, and base/earned/nodes aggregation. Anti-gaming notes: @@ -166,7 +166,7 @@ async def score_pr( if repo_config.fixed_base_score is not None: # Only the base score is overridden. Token fields stay token-derived so - # eligibility, pioneer, and reporting gates keep their evidence signal. + # eligibility and reporting gates keep their evidence signal. scored.base_score = repo_config.fixed_base_score _calculate_pr_multipliers(scored, repo_config) diff --git a/gittensor/validator/oss_contributions/reward.py b/gittensor/validator/oss_contributions/reward.py index c92ae748..1ab1e772 100644 --- a/gittensor/validator/oss_contributions/reward.py +++ b/gittensor/validator/oss_contributions/reward.py @@ -143,7 +143,7 @@ async def get_rewards( if penalized_uids: self.evaluation_cache.evict_many(penalized_uids) - # Finalize scores: apply eligibility gate, credibility, pioneer dividends, collateral + # Finalize scores: apply eligibility gate, credibility, collateral finalize_miner_scores(miner_evaluations, master_repositories) return miner_evaluations, cached_uids, penalized_uids diff --git a/gittensor/validator/oss_contributions/scoring.py b/gittensor/validator/oss_contributions/scoring.py index 7398e274..57786487 100644 --- a/gittensor/validator/oss_contributions/scoring.py +++ b/gittensor/validator/oss_contributions/scoring.py @@ -1,8 +1,7 @@ # The MIT License (MIT) # Copyright © 2025 Entrius -from datetime import datetime -from typing import TYPE_CHECKING, Dict, Optional, Tuple +from typing import TYPE_CHECKING, Dict, Optional import bittensor as bt @@ -16,10 +15,6 @@ MAX_OPEN_PR_THRESHOLD, OPEN_PR_COLLATERAL_PERCENT, OPEN_PR_THRESHOLD_TOKEN_SCORE, - PIONEER_DIVIDEND_MAX_RATIO, - PIONEER_DIVIDEND_RATE_1ST, - PIONEER_DIVIDEND_RATE_2ND, - PIONEER_DIVIDEND_RATE_REST, REVIEW_PENALTY_RATE, ) from gittensor.validator.oss_contributions.credibility import check_eligibility @@ -80,76 +75,6 @@ def calculate_pr_spam_penalty_multiplier(total_open_prs: int, total_token_score: return 1.0 if total_open_prs <= threshold else 0.0 -def calculate_pioneer_dividends( - miner_evaluations: Dict[int, MinerEvaluation], -) -> None: - """Determine pioneers and set pioneer_rank + pioneer_dividend on each PR. - - For each repo, the pioneer is the miner with the earliest merged PR that - passes the quality gate (is_pioneer_eligible). The pioneer's earliest PR - on that repo earns a dividend based on ALL followers' earned_scores (post- - multiplier), using per-position rates (30%/20%/10%). The dividend uses the - follower's multipliers, not the pioneer's — so it reflects follower quality. - - Must be called AFTER all earned_scores have been computed. - """ - pr_index: Dict[str, Dict[int, list]] = {} - repo_contributions: Dict[str, Dict[int, Tuple[datetime, int, float]]] = {} - - for uid, evaluation in miner_evaluations.items(): - for pr in evaluation.merged_prs: - if not pr.is_pioneer_eligible(): - continue - assert pr.merged_at is not None - repo = pr.repository_full_name - pr_index.setdefault(repo, {}).setdefault(uid, []).append(pr) - - current = repo_contributions.setdefault(repo, {}).get(uid) - if current is None: - repo_contributions[repo][uid] = (pr.merged_at, pr.number, pr.earned_score) - else: - earliest_at, earliest_num, total_score = current - new_total = total_score + pr.earned_score - if pr.merged_at < earliest_at or (pr.merged_at == earliest_at and pr.number < earliest_num): - repo_contributions[repo][uid] = (pr.merged_at, pr.number, new_total) - else: - repo_contributions[repo][uid] = (earliest_at, earliest_num, new_total) - - for repo, uid_entries in repo_contributions.items(): - sorted_uids = sorted(uid_entries.items(), key=lambda x: (x[1][0], x[1][1])) - - for rank_pos, (uid, _) in enumerate(sorted_uids): - for pr in pr_index[repo][uid]: - pr.pioneer_rank = rank_pos + 1 - - dividend = 0.0 - for pos, (_, entry) in enumerate(sorted_uids[1:]): - follower_earned = entry[2] - if pos == 0: - dividend += follower_earned * PIONEER_DIVIDEND_RATE_1ST - elif pos == 1: - dividend += follower_earned * PIONEER_DIVIDEND_RATE_2ND - else: - dividend += follower_earned * PIONEER_DIVIDEND_RATE_REST - - if dividend <= 0: - continue - - pioneer_uid = sorted_uids[0][0] - pioneer_pr_number = sorted_uids[0][1][1] - pioneer_pr = next(pr for pr in pr_index[repo][pioneer_uid] if pr.number == pioneer_pr_number) - max_dividend = pioneer_pr.earned_score * PIONEER_DIVIDEND_MAX_RATIO - capped = min(dividend, max_dividend) - pioneer_pr.pioneer_dividend = round(capped, 2) - pioneer_pr.earned_score = round(pioneer_pr.earned_score + pioneer_pr.pioneer_dividend, 2) - - cap_note = f' (capped from {dividend:.2f})' if capped < dividend else '' - bt.logging.info( - f'Pioneer dividend | repo={repo} pioneer=uid {pioneer_uid} ' - f'followers={len(sorted_uids) - 1} dividend={capped:.2f}{cap_note}' - ) - - def _pr_bypasses_eligibility( pr: 'ScoredPR', master_repositories: Dict[str, RepositoryConfig], @@ -162,7 +87,7 @@ def finalize_miner_scores( miner_evaluations: Dict[int, MinerEvaluation], master_repositories: Optional[Dict[str, RepositoryConfig]] = None, ) -> None: - """Finalize all miner scores: compute earned_scores, then apply pioneer dividends, then collateral.""" + """Finalize all miner scores: compute earned_scores, then collateral, then aggregate.""" bt.logging.info('**Finalizing miner scores**') master_repositories = master_repositories or {} @@ -242,10 +167,7 @@ def finalize_miner_scores( evaluation.total_leaf_count += pr.leaf_count evaluation.total_leaf_score += pr.leaf_score - # Phase 2: Calculate pioneer dividends from follower earned_scores - calculate_pioneer_dividends(miner_evaluations) - - # Phase 3: Aggregate totals (including dividends), collateral, logging + # Phase 2: Aggregate totals, apply collateral, log summary for uid, evaluation in miner_evaluations.items(): if not evaluation: continue @@ -254,7 +176,6 @@ def finalize_miner_scores( if not has_contributions: continue - # Aggregate scores (earned_score now includes pioneer_dividend from Phase 2). for pr in evaluation.merged_prs: evaluation.base_total_score += pr.base_score evaluation.total_score += pr.earned_score diff --git a/gittensor/validator/storage/queries.py b/gittensor/validator/storage/queries.py index 201d79e0..477fd90b 100644 --- a/gittensor/validator/storage/queries.py +++ b/gittensor/validator/storage/queries.py @@ -46,7 +46,7 @@ number, repository_full_name, uid, hotkey, github_id, title, author_login, merged_at, pr_created_at, pr_state, base_score, issue_multiplier, - open_pr_spam_multiplier, pioneer_dividend, pioneer_rank, time_decay_multiplier, + open_pr_spam_multiplier, time_decay_multiplier, credibility_multiplier, review_quality_multiplier, label_multiplier, label, earned_score, collateral_score, additions, deletions, commits, total_nodes_scored, @@ -56,7 +56,7 @@ %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, - %s, %s, %s, %s, + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, @@ -74,8 +74,6 @@ base_score = EXCLUDED.base_score, issue_multiplier = EXCLUDED.issue_multiplier, open_pr_spam_multiplier = EXCLUDED.open_pr_spam_multiplier, - pioneer_dividend = EXCLUDED.pioneer_dividend, - pioneer_rank = EXCLUDED.pioneer_rank, time_decay_multiplier = EXCLUDED.time_decay_multiplier, credibility_multiplier = EXCLUDED.credibility_multiplier, review_quality_multiplier = EXCLUDED.review_quality_multiplier, diff --git a/gittensor/validator/storage/repository.py b/gittensor/validator/storage/repository.py index 0e664310..d123b3f2 100644 --- a/gittensor/validator/storage/repository.py +++ b/gittensor/validator/storage/repository.py @@ -180,8 +180,6 @@ def store_pull_requests_bulk(self, pull_requests: List[PullRequest], commit: boo pr.base_score, pr.issue_multiplier, pr.open_pr_spam_multiplier, - pr.pioneer_dividend, - pr.pioneer_rank, pr.time_decay_multiplier, pr.credibility_multiplier, pr.review_quality_multiplier, diff --git a/gittensor/validator/utils/load_weights.py b/gittensor/validator/utils/load_weights.py index c7ba1fa1..d1c8fee5 100644 --- a/gittensor/validator/utils/load_weights.py +++ b/gittensor/validator/utils/load_weights.py @@ -30,7 +30,6 @@ class RepositoryConfig: Attributes: emission_share: Fraction of the combined scoring pool allocated to this repo issue_discovery_share: Fraction of the repo allocation reserved for issue discovery - inactive_at: ISO timestamp when repository became inactive (None if active) additional_acceptable_branches: List of additional branch patterns to accept (None if only default branch) trusted_label_pipeline: When True, scoring labels count regardless of actor — including GitHub Apps that surface as ``actor_association=NULL``. @@ -49,7 +48,6 @@ class RepositoryConfig: emission_share: float issue_discovery_share: float = DEFAULT_ISSUE_DISCOVERY_SHARE - inactive_at: Optional[str] = None additional_acceptable_branches: Optional[List[str]] = None trusted_label_pipeline: bool = False label_multipliers: Optional[Dict[str, float]] = None @@ -162,7 +160,6 @@ def load_master_repo_weights() -> Dict[str, RepositoryConfig]: 'issue_discovery_share', metadata.get('issue_discovery_share', DEFAULT_ISSUE_DISCOVERY_SHARE), ), - inactive_at=metadata.get('inactive_at'), additional_acceptable_branches=metadata.get('additional_acceptable_branches'), trusted_label_pipeline=bool(metadata.get('trusted_label_pipeline', False)), label_multipliers=( diff --git a/gittensor/validator/weights/master_repositories.json b/gittensor/validator/weights/master_repositories.json index 74dd91eb..cfbd1226 100644 --- a/gittensor/validator/weights/master_repositories.json +++ b/gittensor/validator/weights/master_repositories.json @@ -1,6 +1,6 @@ { "entrius/allways": { - "emission_share": 0.05017719, + "emission_share": 0.05018, "issue_discovery_share": 1.0, "label_multipliers": { "bug": 1.25, @@ -10,7 +10,7 @@ "trusted_label_pipeline": true }, "entrius/allways-ui": { - "emission_share": 0.01106825, + "emission_share": 0.01107, "issue_discovery_share": 0.0, "label_multipliers": { "bug": 1.1, @@ -21,7 +21,7 @@ "trusted_label_pipeline": true }, "entrius/das-github-mirror": { - "emission_share": 0.0200651, + "emission_share": 0.01180, "issue_discovery_share": 0.5, "label_multipliers": { "bug": 1.25, @@ -31,7 +31,7 @@ "trusted_label_pipeline": true }, "entrius/gittensor": { - "emission_share": 0.101274, + "emission_share": 0.10127, "issue_discovery_share": 0.25, "label_multipliers": { "bug": 1.1, @@ -42,7 +42,7 @@ "trusted_label_pipeline": true }, "entrius/gittensor-ui": { - "emission_share": 0.03017, + "emission_share": 0.01966, "issue_discovery_share": 0.0, "label_multipliers": { "bug": 1.1, @@ -55,12 +55,24 @@ "entrius/oc-1": { "default_label_multiplier": 0.0, "eligibility_mode": false, - "emission_share": 0.52782093, + "emission_share": 0.00000, "fixed_base_score": 1.0, "issue_discovery_share": 0.0, "label_multipliers": { "benchmark-improvement": 1.0 }, "trusted_label_pipeline": true + }, + "Geniepod/genie-claw": { + "emission_share": 0.04500, + "issue_discovery_share": 0.0 + }, + "MkDev11/gittensor-hub": { + "emission_share": 0.03500, + "issue_discovery_share": 0.0 + }, + "seroperson/jvm-live-reload": { + "emission_share": 0.03000, + "issue_discovery_share": 0.0 } } diff --git a/tests/validator/oss_contributions/mirror/test_adapters.py b/tests/validator/oss_contributions/mirror/test_adapters.py index d51bf192..cd25fdef 100644 --- a/tests/validator/oss_contributions/mirror/test_adapters.py +++ b/tests/validator/oss_contributions/mirror/test_adapters.py @@ -172,8 +172,6 @@ def test_full_adapter_field_mapping(self): scored.issue_multiplier = 1.33 scored.open_pr_spam_multiplier = 1.0 scored.collateral_score = 0.0 - scored.pioneer_dividend = 2.0 - scored.pioneer_rank = 1 scored.code_density = 0.85 scored.structural_count = 10 scored.structural_score = 50.0 @@ -214,8 +212,6 @@ def test_full_adapter_field_mapping(self): assert adapted.credibility_multiplier == 0.9 assert adapted.issue_multiplier == 1.33 assert adapted.open_pr_spam_multiplier == 1.0 - assert adapted.pioneer_dividend == 2.0 - assert adapted.pioneer_rank == 1 assert adapted.total_nodes_scored == 30 assert adapted.code_density == 0.85 # file_changes / issues left None — written separately @@ -252,5 +248,4 @@ def test_default_scoring_fields_when_pr_not_yet_scored(self): adapted = mirror_scored_pr_to_legacy_pull_request(scored, 1, 'hk', 'gid') assert adapted.base_score == 0.0 assert adapted.earned_score == 0.0 - assert adapted.pioneer_rank == 0 assert adapted.label is None diff --git a/tests/validator/oss_contributions/mirror/test_load.py b/tests/validator/oss_contributions/mirror/test_load.py index 91b6369e..214fa52c 100644 --- a/tests/validator/oss_contributions/mirror/test_load.py +++ b/tests/validator/oss_contributions/mirror/test_load.py @@ -200,34 +200,10 @@ def test_dev_mode_bypasses_maintainer_skip(self, monkeypatch): # ============================================================================ -# Inactive repo + stale closed PR +# Stale closed PR # ============================================================================ -class TestInactiveRepo: - def test_pr_created_after_inactive_at_dropped(self): - client = Mock() - client.get_miner_pulls.return_value = _build_response( - [ - # Created on 2026-04-15, repo became inactive on 2026-04-10 → drop - _pr_dict(1, created_at='2026-04-15T00:00:00Z'), - # Created on 2026-04-05 (before inactive_at) → keep - _pr_dict(2, created_at='2026-04-05T00:00:00Z'), - ] - ) - repos = { - 'entrius/gittensor-ui': RepositoryConfig( - emission_share=0.5, - inactive_at='2026-04-10T00:00:00Z', - ), - } - eval_ = _eval() - load_miner_prs(eval_, repos, client=client) - - assert len(eval_.merged_prs) == 1 - assert eval_.merged_prs[0].pr.pr_number == 2 - - class TestStaleClosedPR: def test_closed_pr_created_before_lookback_dropped(self): # Lookback is 35 days before "now"; a CLOSED PR created 50 days ago should drop diff --git a/tests/validator/oss_contributions/mirror/test_pioneer.py b/tests/validator/oss_contributions/mirror/test_pioneer.py deleted file mode 100644 index d5016398..00000000 --- a/tests/validator/oss_contributions/mirror/test_pioneer.py +++ /dev/null @@ -1,132 +0,0 @@ -"""Unit tests for the unified ``calculate_pioneer_dividends`` exercising -``merged_prs`` (ScoredPR shape). - -Parallels tests/validator/test_pioneer_dividend.py, which exercises the same -function over legacy ``merged_pull_requests``. -""" - -import pytest - -pioneer_module = pytest.importorskip('gittensor.validator.oss_contributions.scoring') -scored_pr_module = pytest.importorskip('gittensor.validator.oss_contributions.mirror.scored_pr') -mirror_models = pytest.importorskip('gittensor.utils.mirror.models') -classes = pytest.importorskip('gittensor.classes') - -calculate_pioneer_dividends = pioneer_module.calculate_pioneer_dividends -ScoredPR = scored_pr_module.ScoredPR -MirrorPullRequest = mirror_models.MirrorPullRequest -MinerEvaluation = classes.MinerEvaluation - - -def _scored( - pr_number: int, - merged_at: str = '2026-04-15T00:00:00Z', - repo: str = 'entrius/gittensor-ui', - earned_score: float = 10.0, - token_score: float = 100.0, -) -> ScoredPR: - pr = MirrorPullRequest.from_dict( - { - 'repo_full_name': repo, - 'pr_number': pr_number, - 'title': 't', - 'body': 'b', - 'state': 'MERGED', - 'author_github_id': '1', - 'author_login': 'a', - 'author_association': 'CONTRIBUTOR', - 'created_at': '2026-04-10T00:00:00Z', - 'closed_at': merged_at, - 'merged_at': merged_at, - 'last_edited_at': None, - 'edited_after_merge': False, - 'hours_since_merge': 1.0, - 'merged_by_login': 'm', - 'base_ref': 'test', - 'head_sha': 'h', - 'base_sha': 'b', - 'merge_base_sha': 'mb', - 'additions': 1, - 'deletions': 0, - 'commits_count': 1, - 'scoring_data_stored': True, - 'review_summary': {'maintainer_changes_requested_count': 0}, - 'labels': [], - 'linked_issues': [], - } - ) - scored = ScoredPR(pr=pr) - scored.token_score = token_score - scored.earned_score = earned_score - return scored - - -def _eval_with(uid: int, scored_prs: list) -> MinerEvaluation: - me = MinerEvaluation(uid=uid, hotkey=f'hk{uid}', github_id=str(uid)) - me.merged_prs = scored_prs - return me - - -class TestMirrorPioneer: - def test_earliest_merged_gets_rank_1(self): - uid1_pr = _scored(pr_number=1, merged_at='2026-04-10T00:00:00Z') - uid2_pr = _scored(pr_number=2, merged_at='2026-04-12T00:00:00Z') - miner_evals = { - 1: _eval_with(1, [uid1_pr]), - 2: _eval_with(2, [uid2_pr]), - } - calculate_pioneer_dividends(miner_evals) - - assert uid1_pr.pioneer_rank == 1 - assert uid2_pr.pioneer_rank == 2 - - def test_pioneer_receives_dividend(self): - pioneer_pr = _scored(pr_number=1, merged_at='2026-04-10T00:00:00Z', earned_score=5.0) - follower_pr = _scored(pr_number=2, merged_at='2026-04-12T00:00:00Z', earned_score=10.0) - miner_evals = { - 1: _eval_with(1, [pioneer_pr]), - 2: _eval_with(2, [follower_pr]), - } - calculate_pioneer_dividends(miner_evals) - - # Pioneer dividend is some positive value (rate varies by position) - assert pioneer_pr.pioneer_dividend > 0 - # earned_score gets incremented by dividend - assert pioneer_pr.earned_score == round(5.0 + pioneer_pr.pioneer_dividend, 2) - - def test_ineligible_pr_skipped(self): - """PRs that fail is_pioneer_eligible (e.g. token_score below threshold) - are excluded from pioneer ranking.""" - low_score = _scored(pr_number=1, token_score=1.0) # below MIN_TOKEN_SCORE_FOR_BASE_SCORE (5) - high_score = _scored(pr_number=2, token_score=100.0) - miner_evals = { - 1: _eval_with(1, [low_score]), - 2: _eval_with(2, [high_score]), - } - calculate_pioneer_dividends(miner_evals) - - # low_score didn't participate; high_score is the only/pioneer - assert low_score.pioneer_rank == 0 # default, never touched - assert high_score.pioneer_rank == 1 - - def test_per_repo_isolation(self): - """Pioneer is per-repo. Different repos get independent rankings.""" - repo_a_uid1 = _scored(pr_number=1, repo='foo/a', merged_at='2026-04-10T00:00:00Z') - repo_b_uid1 = _scored(pr_number=2, repo='foo/b', merged_at='2026-04-20T00:00:00Z') - miner_evals = {1: _eval_with(1, [repo_a_uid1, repo_b_uid1])} - - calculate_pioneer_dividends(miner_evals) - - # Single miner on each repo — each is their own pioneer (rank 1) - assert repo_a_uid1.pioneer_rank == 1 - assert repo_b_uid1.pioneer_rank == 1 - - def test_empty_evaluations_no_crash(self): - calculate_pioneer_dividends({}) # should not raise - - def test_no_pioneer_eligible_prs_no_dividend(self): - pr = _scored(pr_number=1, token_score=0.0) - miner_evals = {1: _eval_with(1, [pr])} - calculate_pioneer_dividends(miner_evals) - assert pr.pioneer_dividend == 0.0 - assert pr.pioneer_rank == 0 diff --git a/tests/validator/oss_contributions/mirror/test_scored_pr.py b/tests/validator/oss_contributions/mirror/test_scored_pr.py index 8ed0443c..9ddd7a75 100644 --- a/tests/validator/oss_contributions/mirror/test_scored_pr.py +++ b/tests/validator/oss_contributions/mirror/test_scored_pr.py @@ -2,7 +2,6 @@ Covers: - Composition: raw response data accessed via .pr.; scoring fields default neutrally -- is_pioneer_eligible respects merged + token_score gate - calculate_final_earned_score multiplies base by every multiplier """ @@ -75,32 +74,9 @@ def test_scoring_fields_default_neutral(self): assert scored.base_score == 0.0 assert scored.earned_score == 0.0 assert scored.token_score == 0.0 - assert scored.pioneer_rank == 0 assert scored.files is None -class TestPioneerEligible: - def test_unmerged_not_eligible(self): - scored = ScoredPR(pr=_make_pr(state='OPEN', merged_at_iso=None)) - scored.token_score = 100.0 - assert scored.is_pioneer_eligible() is False - - def test_merged_below_token_threshold_not_eligible(self): - scored = ScoredPR(pr=_make_pr()) - scored.token_score = 1.0 # below MIN_TOKEN_SCORE_FOR_BASE_SCORE (5) - assert scored.is_pioneer_eligible() is False - - def test_merged_at_threshold_eligible(self): - scored = ScoredPR(pr=_make_pr()) - scored.token_score = 5.0 # equals MIN_TOKEN_SCORE_FOR_BASE_SCORE - assert scored.is_pioneer_eligible() is True - - def test_merged_above_threshold_eligible(self): - scored = ScoredPR(pr=_make_pr()) - scored.token_score = 50.0 - assert scored.is_pioneer_eligible() is True - - class TestCalculateFinalEarnedScore: def test_neutral_multipliers_returns_base(self): scored = ScoredPR(pr=_make_pr()) diff --git a/tests/validator/test_load_weights.py b/tests/validator/test_load_weights.py index 88b78ad4..d6276cc9 100644 --- a/tests/validator/test_load_weights.py +++ b/tests/validator/test_load_weights.py @@ -294,52 +294,6 @@ def test_live_mirror_scoring_fields_have_valid_shape(self): ) -class TestBannedOrganizations: - """Tests ensuring banned organizations are not active in the repository list. - - Any repositories from these orgs MUST be marked as inactive. - """ - - # orgs may be banned for: - # - exploitative PR manipulation - # - explicit removal request - BANNED_ORGS = [ - 'conda', - 'conda-incubator', - 'conda-archive', - 'louislam', - 'python', - 'fastapi', - 'astral-sh', - 'astropy', - 'numpy', - 'scipy', - ] - - def test_banned_org_repos_are_inactive(self): - """Repositories from banned organizations must be marked as inactive.""" - repos = load_master_repo_weights() - - for repo_name, config in repos.items(): - org = repo_name.split('/')[0] if '/' in repo_name else None - if org in self.BANNED_ORGS: - assert config.inactive_at is not None, ( - f'Repository {repo_name} from banned org {org} must be marked inactive' - ) - - def test_no_active_banned_org_repos(self): - """Count of active repositories from banned orgs should be zero.""" - repos = load_master_repo_weights() - - active_banned = [] - for repo_name, config in repos.items(): - org = repo_name.split('/')[0] if '/' in repo_name else None - if org in self.BANNED_ORGS and config.inactive_at is None: - active_banned.append(repo_name) - - assert len(active_banned) == 0, f'Found {len(active_banned)} active repos from banned orgs: {active_banned}' - - class TestRepositoryEmissionShare: """Tests for bounded repo emission_share loading.""" diff --git a/tests/validator/test_pioneer_dividend.py b/tests/validator/test_pioneer_dividend.py deleted file mode 100644 index 3c57e3bb..00000000 --- a/tests/validator/test_pioneer_dividend.py +++ /dev/null @@ -1,231 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2025 Entrius - -"""Tests for pioneer dividend mechanism.""" - -from datetime import datetime, timedelta, timezone - -import pytest - -from gittensor.classes import MinerEvaluation, PRState -from gittensor.constants import ( - MIN_TOKEN_SCORE_FOR_BASE_SCORE, - PIONEER_DIVIDEND_MAX_RATIO, - PIONEER_DIVIDEND_RATE_1ST, - PIONEER_DIVIDEND_RATE_2ND, -) -from gittensor.validator.oss_contributions.scoring import ( - calculate_pioneer_dividends, -) -from tests.validator.conftest import PRBuilder - -# ========================================================================== -# Fixtures -# ========================================================================== - - -@pytest.fixture -def builder(): - return PRBuilder() - - -# ========================================================================== -# TestPioneerEligibility -# ========================================================================== - - -class TestPioneerEligibility: - """Tests for PullRequest.is_pioneer_eligible instance method.""" - - def test_eligible_when_merged_with_token_score(self, builder): - pr = builder.create(state=PRState.MERGED, uid=1) - assert pr.is_pioneer_eligible() - - def test_ineligible_when_below_token_score(self, builder): - pr = builder.create(state=PRState.MERGED, uid=1, token_score=MIN_TOKEN_SCORE_FOR_BASE_SCORE - 1) - assert not pr.is_pioneer_eligible() - - def test_ineligible_when_open(self, builder): - pr = builder.create(state=PRState.OPEN, uid=1) - assert not pr.is_pioneer_eligible() - - def test_ineligible_when_closed(self, builder): - pr = builder.create(state=PRState.CLOSED, uid=1) - assert not pr.is_pioneer_eligible() - - -# ========================================================================== -# TestPioneerDividendCalculation -# ========================================================================== - - -class TestPioneerDividendCalculation: - """Tests for calculate_pioneer_dividends function.""" - - def _make_eval(self, uid, prs): - """Helper to create a MinerEvaluation with given merged PRs.""" - eval_ = MinerEvaluation(uid=uid, hotkey=f'hotkey_{uid}') - eval_.merged_prs = prs - return eval_ - - def test_single_contributor_no_dividend(self, builder): - """Pioneer with no followers gets no dividend.""" - now = datetime.now(timezone.utc) - pr = builder.create( - state=PRState.MERGED, - uid=1, - repo='test/repo', - merged_at=now, - earned_score=100.0, - ) - evals = {1: self._make_eval(1, [pr])} - calculate_pioneer_dividends(evals) - assert pr.pioneer_dividend == 0.0 - - def test_two_contributors_pioneer_gets_dividend(self, builder): - """Pioneer gets dividend from the 1st follower.""" - now = datetime.now(timezone.utc) - pioneer_pr = builder.create( - state=PRState.MERGED, - uid=1, - repo='test/repo', - merged_at=now - timedelta(days=5), - earned_score=100.0, - ) - follower_pr = builder.create( - state=PRState.MERGED, - uid=2, - repo='test/repo', - merged_at=now - timedelta(days=1), - earned_score=80.0, - ) - evals = { - 1: self._make_eval(1, [pioneer_pr]), - 2: self._make_eval(2, [follower_pr]), - } - calculate_pioneer_dividends(evals) - - expected_dividend = min(80.0 * PIONEER_DIVIDEND_RATE_1ST, 100.0 * PIONEER_DIVIDEND_MAX_RATIO) - assert pioneer_pr.pioneer_dividend == round(expected_dividend, 2) - assert pioneer_pr.pioneer_rank == 1 - assert follower_pr.pioneer_rank == 2 - - def test_three_contributors_diminishing_rates(self, builder): - """Pioneer dividend diminishes across follower positions.""" - now = datetime.now(timezone.utc) - pioneer_pr = builder.create( - state=PRState.MERGED, - uid=1, - repo='test/repo', - merged_at=now - timedelta(days=10), - earned_score=200.0, - ) - f1_pr = builder.create( - state=PRState.MERGED, - uid=2, - repo='test/repo', - merged_at=now - timedelta(days=5), - earned_score=100.0, - ) - f2_pr = builder.create( - state=PRState.MERGED, - uid=3, - repo='test/repo', - merged_at=now - timedelta(days=1), - earned_score=80.0, - ) - evals = { - 1: self._make_eval(1, [pioneer_pr]), - 2: self._make_eval(2, [f1_pr]), - 3: self._make_eval(3, [f2_pr]), - } - calculate_pioneer_dividends(evals) - - expected = 100.0 * PIONEER_DIVIDEND_RATE_1ST + 80.0 * PIONEER_DIVIDEND_RATE_2ND - expected_capped = min(expected, 200.0 * PIONEER_DIVIDEND_MAX_RATIO) - assert pioneer_pr.pioneer_dividend == round(expected_capped, 2) - - def test_dividend_capped(self, builder): - """Pioneer dividend is capped at PIONEER_DIVIDEND_MAX_RATIO × pioneer's earned_score.""" - now = datetime.now(timezone.utc) - pioneer_pr = builder.create( - state=PRState.MERGED, - uid=1, - repo='test/repo', - merged_at=now - timedelta(days=10), - earned_score=10.0, - ) - # Large follower scores - followers = [] - for i in range(5): - pr = builder.create( - state=PRState.MERGED, - uid=i + 2, - repo='test/repo', - merged_at=now - timedelta(days=5 - i), - earned_score=500.0, - ) - followers.append(pr) - - evals = {1: self._make_eval(1, [pioneer_pr])} - for i, fpr in enumerate(followers): - evals[i + 2] = self._make_eval(i + 2, [fpr]) - - calculate_pioneer_dividends(evals) - - max_expected = 10.0 * PIONEER_DIVIDEND_MAX_RATIO - assert pioneer_pr.pioneer_dividend == round(max_expected, 2) - - def test_different_repos_independent(self, builder): - """Pioneer dividends are independent per repository.""" - now = datetime.now(timezone.utc) - pr_a = builder.create( - state=PRState.MERGED, - uid=1, - repo='test/repo-a', - merged_at=now - timedelta(days=5), - earned_score=100.0, - ) - pr_b = builder.create( - state=PRState.MERGED, - uid=2, - repo='test/repo-b', - merged_at=now - timedelta(days=5), - earned_score=100.0, - ) - evals = { - 1: self._make_eval(1, [pr_a]), - 2: self._make_eval(2, [pr_b]), - } - calculate_pioneer_dividends(evals) - - # No followers on either repo, so no dividends - assert pr_a.pioneer_dividend == 0.0 - assert pr_b.pioneer_dividend == 0.0 - - def test_ineligible_prs_excluded(self, builder): - """PRs below token score threshold don't participate in pioneer calculation.""" - now = datetime.now(timezone.utc) - pioneer_pr = builder.create( - state=PRState.MERGED, - uid=1, - repo='test/repo', - merged_at=now - timedelta(days=5), - earned_score=100.0, - ) - ineligible_pr = builder.create( - state=PRState.MERGED, - uid=2, - repo='test/repo', - merged_at=now - timedelta(days=1), - earned_score=50.0, - token_score=MIN_TOKEN_SCORE_FOR_BASE_SCORE - 1, - ) - evals = { - 1: self._make_eval(1, [pioneer_pr]), - 2: self._make_eval(2, [ineligible_pr]), - } - calculate_pioneer_dividends(evals) - - # Ineligible follower doesn't count - assert pioneer_pr.pioneer_dividend == 0.0