Skip to content

feat(validator): redistribute ineligible repo emissions to eligible repos#1547

Merged
LandynDev merged 2 commits into
testfrom
feat/normalize-repo-emissions
Jun 29, 2026
Merged

feat(validator): redistribute ineligible repo emissions to eligible repos#1547
LandynDev merged 2 commits into
testfrom
feat/normalize-repo-emissions

Conversation

@LandynDev

Copy link
Copy Markdown
Collaborator

Problem

A repo assigned an emission_share but with no eligible recipients this round (no registered maintainer, no PR/issue scorers, no contributions yet) recycled its entire slice to UID 0 — burning those emissions instead of releasing them.

Change

In calculate_repo_emission_breakdown, renormalize configured emission_share across only the repos that pay at least one miner this round, then scale each eligible repo's slice by configured_total / eligible_total. Ineligible repos' shares are redistributed to eligible repos in proportion to their configured weight, so the full configured pool is always released.

  • Eligibility mirrors the existing distribution gates: PR scorers (when issue_discovery_share < 1.0), issue scorers (when > 0.0), or a maintainer carve-out with a registered maintainer.
  • Registry slack (configured shares summing < 1.0) is untouched and still recycles.
  • No eligible repos anywhere → the pool recycles as before.
  • Per-repo internal structure (maintainer cut, PR/issue split, intra-repo recycle) is preserved.

Tests

  • Replaced test_empty_repo_slice_recycles_without_redistribution with redistribution coverage (single eligible repo + weight-proportional split across multiple eligible repos).
  • All existing maintainer-cut, registry-slack, and sum-to-one tests pass unchanged.

@anderdc

anderdc commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Nice work — this is a strict improvement (burn never goes up, slices conserve to configured). But it doesn't fully hit the "release the whole pool" goal, and it opens a small gaming vector. Flagging before merge.

The issue: repo eligibility is binary at the whole-repo level, but a repo can be partially eligible.

_eligible_repo_shares marks a repo eligible at its full emission_share if it has a registered maintainer — even with zero PR/issue scorers. Redistribution then scales that repo's whole slice up by multiplier, including the maintainer carve-out. Two problems:

  1. It re-burns. Only maintainer_cut of the scaled slice pays the maintainer; the miner portion (scoring_slice, the pr_total <= 0 and issue_total <= 0 branch) still recycles to UID 0. So redistribution funnels more money into a repo that then burns the miner half — net burn drops vs before, but it's not zero.
  2. It inflates the maintainer cut (gaming vector). Because the multiplier scales the whole slice, a maintainer's carve-out grows when other repos go dead — a windfall for presence, not work. Worse incentive direction: it rewards a repo where miners aren't scoring.

Proposed fix: split the repo into two piles and treat them separately.

  • Maintainer cut = fixed slice of the repo's own base share (maintainer_cut * emission_share * OSS). Paid to a present maintainer, never scaled by redistribution. This is the anti-gaming guarantee.
  • Scoring money = everything else. Pool all of it subnet-wide and hand it only to repos that actually have PR/issue scorers this round, weighted by configured emission_share, then the existing intra-repo PR/issue/score split.

So the multiplier applies to the scoring slice only, not the whole repo slice. Anything that would've burned — dead repos, the miner-half of maintainer-only repos, unclaimed maintainer cuts — flows into the scoring pool instead.

Rough shape:

  • Pay maintainer cuts at base rate first; scoring_pool = configured*OSS - sum(maintainer cuts paid).
  • "Active" = has a PR scorer (when issue_discovery_share < 1.0) or issue scorer (when > 0.0). Maintainer presence does not make a repo active.
  • Active repo i gets scoring_pool * emission_share_i / sum(emission_share over active repos), then splits as it does today.
  • No active repos anywhere -> scoring_pool recycles, same as now.

Conserves to configured*OSS whenever ≥1 miner scored anywhere, and a maintainer's take no longer moves based on other repos or their own miners' activity. Registry slack handling stays as-is.

You'll figure out the cleanest way to thread it — main thing is the maintainer pile and the scoring pile never touch.

@LandynDev

Copy link
Copy Markdown
Collaborator Author

Good catch — addressed in 074808c. Split into the two piles you described:

  • Maintainer cut is now paid at the repo's base rate (maintainer_cut * emission_share * OSS) and is never scaled by redistribution. A maintainer's take no longer moves when other repos go dead.
  • Scoring pool = configured*OSS - maintainer cuts paid, released only to repos with PR/issue scorers this round. Maintainer presence no longer marks a repo active, so a maintainer-only repo's miner half (and dead repos' shares) flow into the pool instead of re-burning.

One deviation from the literal sketch: I weight the scoring pool by each active repo's post-cut scoring share ((1 - cut)*emission_share) rather than full emission_share. Weighting by full share lets a repo with a paid maintainer cut double-dip (its cut is paid and its full weight competes for scoring), which would shift steady-state splits even with no dead repos. Post-cut weighting conserves exactly to configured*OSS and is a no-op vs today when nothing is dead.

New tests lock it in:

  • test_maintainer_cut_not_inflated_by_redistribution — maintainer-only repo (0.4 share, 0.5 cut) + active repo + dead repo: maintainer gets exactly 0.5*0.4*OSS (not scaled by the 2× multiplier), active scorer absorbs the rest, zero burn.
  • test_maintainer_only_repo_recycles_scoring_when_nothing_active — no active repos: maintainer still paid base rate, scoring pool recycles.

603 tests green.

…epos

Ineligible repo slices (no maintainer, no scorers, no contributions) recycled
to UID 0, burning emissions. Renormalize configured emission_share across only
repos that pay a miner this round so the full configured pool is always
released; registry slack (configured shares < 1.0) still recycles.
Address review: pay the maintainer cut at the repo's base rate (never scaled by
redistribution) and redistribute only the scoring pool across repos with actual
PR/issue scorers, weighted by post-cut scoring share. A maintainer-only repo's
miner half now flows to active scorers instead of re-burning, and a maintainer's
take no longer inflates when other repos go dead. Maintainer presence no longer
marks a repo active. With no active repos the scoring pool recycles as before.
@LandynDev LandynDev force-pushed the feat/normalize-repo-emissions branch from 074808c to c249c62 Compare June 29, 2026 17:01
@LandynDev LandynDev merged commit aa411d9 into test Jun 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants