Skip to content

feat(rewards): allocate emissions by bounded per-repo emission_share#1238

Closed
RenzoMXD wants to merge 1 commit into
entrius:testfrom
RenzoMXD:fix/issue-1215-emission-share
Closed

feat(rewards): allocate emissions by bounded per-repo emission_share#1238
RenzoMXD wants to merge 1 commit into
entrius:testfrom
RenzoMXD:fix/issue-1215-emission-share

Conversation

@RenzoMXD
Copy link
Copy Markdown
Contributor

@RenzoMXD RenzoMXD commented May 13, 2026

Closes #1215

Summary

Replaces the per-PR repo_weight_multiplier factor with per-repo emission share allocation at aggregation time. Every PR scores as if its repo contributes 1.0 to the multiplier chain; the final reward is divided proportionally inside each repository's emission_share × OSS_EMISSION_SHARE slice. Allocate-then-distribute, never multiply-then-cap.

Deliverables (mapped 1:1 to #1215)

D1RepositoryConfig.weight renamed to emission_share. Load-time validation enforces per-entry [0, 1] and registry sum ≤ 1.0. Strictly greater than 1.0 is rejected; less than 1.0 is valid and routes the remainder to recycle.

D2 — Each active repo receives exactly emission_share × pool. Single eligible PR claims the full slice; many PRs split it proportionally by earned_score. A high-throughput repo cannot exceed its slice; a low-throughput active repo cannot under-claim it.

D3master_repositories.json migrated in-place. Current values already satisfy the new invariant (sum = 0.71), so 0.29 routes to recycle as registry slack. entrius/oc-1 gets explicit issue_discovery_share: 0.0 per the issue body's canonical optimization-track example.

D4repo_weight_multiplier removed from per-PR scoring, ScoredPR, PullRequest, Issue, mirror adapters, CLI miner-score breakdown, and the storage layer. No code path multiplies by per-repo weight at PR granularity.

D5 / D6tests/validator/test_load_weights.py covers registry invariants (per-entry bounds, sum-≤-1.0 rejection, issue_discovery_share default 0.5). tests/validator/emissions/test_allocate.py adds 15 named tests covering:

  • issue_discovery_share = 0 short-circuit (the oc-1 canonical case) - issue_discovery_share = 1 short-circuit (opposite direction)
  • Within-repo spill in both directions (pr_side_spills_to_issue_side AND issue_side_spills_to_pr_side)
  • Cross-repo linked issues attach to the issue's home repo, not the solving PR's home repo (D8 enforcement)
  • Multi-repo cumulative slices for a single UID
  • Registry slack at sum = 0.8 routes 18% to recycle without redistributing
  • Full-registry round (sum = 1.0, all active) sums to 1.0 with zero recycle
  • No-activity round recycles the entire OSS pool with treasury preserved

D7 — Atomic ship. Both legacy normalize.py modules deleted (no backward-compat path). New allocator has no optional fallback params. SQL persistence drops repo_weight_multiplier from BULK_UPSERT_PULL_REQUESTS and discovery_repo_weight_multiplier from BULK_UPSERT_ISSUES (columns no longer written; existing DB columns can be dropped via ad-hoc ALTER TABLE post-merge, mirroring the precedent from #1202).

D8issue_discovery_share: float = 0.5 added to RepositoryConfig. Defaults to 0.5 (even PR/issue split) per the issue body. entrius/oc-1 sets 0.0 explicitly. Allocation attaches to the issue's home repo via Issue.repository_full_name; cross-repo mechanics (solving-PR token cache, one-issue-per-PR canonical owner, anti-gaming gates) are unchanged.

D9constants.py collapses to:

  • OSS_EMISSION_SHARE = 0.90 (combined scoring pool — replaces both old
    OSS_EMISSION_SHARE = 0.30 and ISSUE_DISCOVERY_EMISSION_SHARE = 0.10)
  • ISSUES_TREASURY_EMISSION_SHARE = 0.10 (down from 0.15, still flat to UID 111)
  • RECYCLE_EMISSION_SHARE removed — recycle becomes natural slack from unclaimed per-repo slices plus registry-level shortfall.

D10 — Recycle and split behavior tested explicitly (see test names in D5/D6 above). Per-round invariant OSS + treasury + recycle = 1.0 asserted in test_round_totals_sum_to_one_when_all_repos_active_and_registry_full.

D11 — Per-repo allocation lives in gittensor/validator/emissions/allocate.py. The emissions/ namespace sits alongside oss_contributions/ and issue_discovery/ so allocation is no longer modeled as an OSS-only concern. Within-repo sub-slices spill across the PR/issue split when exactly one side is empty; both empty recycles the repo slice.

Monetary policy shifts (flagged per D10)

This change carries two intentional policy shifts on top of the architectural one:

  1. Recycle baseline removed. Today recycle is guaranteed 45% of emissions every round regardless of activity. Under this change recycle only fires from (a) repos with no eligible nonzero-scored activity on either side and (b) registry slack when Σ emission_share < 1.0. Round-totals invariant validated by test.

  2. PR-vs-issue split inside the OSS pool. Today's effective split is 75:25 (30 OSS + 10 ID, combined). With OSS_EMISSION_SHARE = 0.90 combined and per-repo issue_discovery_share defaulting to 0.5, repos without an explicit override now split 50:50 within their slice.
    entrius/oc-1 opts out via issue_discovery_share = 0.0.

Other preserved semantics

Per-miner collateral from open PRs is preserved: total_collateral_score × OSS_EMISSION_SHARE is deducted from each UID's allocated reward in forward.apply_collateral_deductions, so open-PR spam still costs the miner emission share.

Test plan

  • ruff check . — clean
  • ruff format --check . — clean
  • pyright — 0 errors
  • pytest tests/739 passing (existing 728 + 11 new in emissions/test_allocate.py)

Closes entrius#1215

Replaces the per-PR repo_weight_multiplier factor with per-repo emission share
allocation at aggregation time. Every PR scores as if its repo contributes 1.0
to the multiplier chain; final reward is divided proportionally inside each
repository's emission_share x OSS_EMISSION_SHARE slice.

Deliverables
- D1 RepositoryConfig.weight renamed to emission_share, validated at load
  time as float in [0, 1] with sum across registry <= 1.0.
- D2 Each active repo receives exactly emission_share x pool, split between
  PR-side and issue-discovery-side by issue_discovery_share. Single eligible
  PR claims the full slice; many PRs split it by per-PR earned_score.
- D3 master_repositories.json migrated in-place (current values already
  satisfy the new invariant; sum is 0.71 so 0.29 routes to recycle).
- D4 repo_weight_multiplier removed from per-PR scoring, ScoredPR,
  PullRequest, Issue, mirror adapters, and the storage layer. No code path
  multiplies by per-repo weight at PR granularity.
- D5/D6 Eight named tests in tests/validator/test_load_weights.py cover
  registry invariants; fifteen named tests in tests/validator/emissions/
  test_allocate.py cover allocation behavior including the
  issue_discovery_share=0 short-circuit (oc-1 case), issue_discovery_share=1
  short-circuit, within-repo spill in both directions, cross-repo linked
  issues attaching to the issue's home repo, multi-repo cumulative slices,
  full-registry zero-recycle invariant, and registry-slack routing.
- D7 Atomic ship: both legacy normalize.py modules deleted, no
  backward-compat parameters on the new allocator, SQL persistence drops
  repo_weight_multiplier and discovery_repo_weight_multiplier from the
  bulk upsert paths in storage/queries.py and storage/repository.py.
- D8 issue_discovery_share field on RepositoryConfig defaults to 0.5;
  entrius/oc-1 sets it to 0.0 explicitly. Issue-discovery rewards attach
  to the issue's home repo via Issue.repository_full_name, not the
  solving PR's home repo.
- D9 constants.py collapses to OSS_EMISSION_SHARE = 0.90 (combined
  scoring pool) and ISSUES_TREASURY_EMISSION_SHARE = 0.10. The flat 45%
  RECYCLE_EMISSION_SHARE is removed -- recycle becomes natural slack from
  unclaimed per-repo slices and any registry-level shortfall.
- D10 Two policy shifts called out below.
- D11 Per-repo allocation lives in validator/emissions/allocate.py; the
  emissions/ namespace sits beside oss_contributions/ and issue_discovery/
  so the allocation step is no longer an OSS-only concept. Within-repo
  sub-slices spill across the PR/issue split when one side is empty.

Monetary policy shifts (flagged per D10)
- Recycle baseline removed. Previously recycle was guaranteed 45% of
  emissions every round. Under this change recycle only fires from
  (a) repos with no eligible nonzero-scored activity on either side and
  (b) registry slack when sum(emission_share) < 1.0.
- PR-vs-issue split inside the OSS pool. Previously 30 OSS + 10 ID = 75:25
  PR-vs-ID. With OSS_EMISSION_SHARE = 0.90 combined and per-repo
  issue_discovery_share defaulting to 0.5, repos without an explicit
  override now split 50:50 within their slice.

Per-miner collateral from open PRs is preserved: total_collateral_score x
OSS_EMISSION_SHARE is deducted from each UID's allocated reward in
forward.apply_collateral_deductions.

Test plan
- ruff check . clean
- ruff format --check . clean
- pyright clean
- pytest tests/ -- 739 passing
@xiao-xiao-mao xiao-xiao-mao Bot added the enhancement New feature or request label May 13, 2026
@anderdc
Copy link
Copy Markdown
Collaborator

anderdc commented May 13, 2026

Issue #1215 deliverables missed: D1 (sum-overflow validation logs but does not raise — a registry summing > 1.0 silently loads), D7 (SQL columns dropped from INSERT/UPDATE in storage/queries.py and storage/repository.py with no migration script). Closing.

@anderdc anderdc closed this May 13, 2026
@RenzoMXD
Copy link
Copy Markdown
Contributor Author

RenzoMXD commented May 13, 2026

Hi, @anderdc
I can see your points. Can you reopen this PR so that I can update my code to completely finish the issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Convert per-repo weight from unbounded multiplier to emission_share (bounded pool slice)

2 participants