Summary
buildRepoDecision (src/services/decision-pack.ts) computes a repo's rewardUpside lane shares straight from the raw registry emissionShare, without the OSS_EMISSION_SHARE (0.9) factor that every other path applies:
// src/services/decision-pack.ts:679-684
const rewardUpside = {
emissionShare: round(config?.emissionShare ?? 0),
directPrShare: round((config?.emissionShare ?? 0) * (1 - (config?.issueDiscoveryShare ?? 0))), // <- no * ossEmissionShare
issueDiscoveryShare: round((config?.emissionShare ?? 0) * (config?.issueDiscoveryShare ?? 0)), // <- no * ossEmissionShare
maintainerCut: round(config?.maintainerCut ?? 0),
};
The canonical lane math in buildScorePreview (src/scoring/preview.ts:270-322) applies the factor and keeps the raw emission share distinct from the OSS-adjusted slices:
// src/scoring/preview.ts:270-275
const emissionShare = clamp(config?.emissionShare ?? 0, 0, 1);
const ossEmissionShare = constant(constants, "OSS_EMISSION_SHARE", 0.9);
const repoSlice = emissionShare * ossEmissionShare; // actual OSS mining pool for the repo
const directPrSlice = repoSlice * (1 - issueDiscoveryShare); // split of the *mining* pool
const issueDiscoverySlice = repoSlice * issueDiscoveryShare;
// ...exposed as laneMath: { repoEmissionShare: emissionShare (raw), repoSlice, directPrSlice, issueDiscoverySlice }
So directPrShare/issueDiscoveryShare in the decision pack are the analogs of preview's directPrSlice/issueDiscoverySlice, but they are computed off the raw emission share and are therefore inflated by 1 / 0.9 ≈ 11.1%.
Why this is wrong
OSS_EMISSION_SHARE is the fraction of a repo's emission that is actually available to OSS mining (the rest is allocated elsewhere). directPrShare/issueDiscoveryShare are meant to represent the tokens a miner can earn in each lane — i.e. a split of the mining pool, exactly like directPrSlice/issueDiscoverySlice in preview.ts.
A direct proof the value is impossible: with issueDiscoveryShare = 0, the current code yields directPrShare = emissionShare — equal to the repo's entire raw emission. But the whole OSS mining pool for the repo is only emissionShare * 0.9 (preview.repoSlice). A single lane's share thus exceeds the total mining pool it is drawn from, which cannot be correct.
The two paths also disagree with each other:
reward-risk.ts (buildRepoRewardRisk, line 283-288) builds its rewardUpside from currentPreview.laneMath.repoSlice / directPrSlice / issueDiscoverySlice — the correctly OSS-adjusted values.
decision-pack.ts re-derives the lane shares itself from the raw emissionShare and drops the factor.
So for the same repo, the reward-risk surface and the decision-pack surface report different lane upside, and the decision-pack one is too high.
Downstream impact
rewardUpside is not cosmetic — it drives the repo ranking. priorityFor (decision-pack.ts:1094-1109) uses:
const upside = Math.max(rewardUpside.directPrShare, rewardUpside.issueDiscoveryShare, rewardUpside.emissionShare * 0.35) * 1000;
// ...priorityScore = clamp(base + upside + history - queuePenalty - blockerPenalty, 0, 100)
Because directPrShare/issueDiscoveryShare are inflated, upside is inflated, so priorityScore is too high. priorityScore is what sorts pursueRepos, cleanupFirst, topActions, etc. (decision-pack.ts:589-590, 642-645). The bias is not uniform: repos whose registry split makes the Math.max resolve to a lane share (rather than the emissionShare * 0.35 floor term, which uses the raw value and is unchanged) get a bigger boost, so the relative ranking of repos is distorted, not just shifted by a constant.
The inflated directPrShare/issueDiscoveryShare are also rendered verbatim in the miner-facing reason strings (decision-pack.ts:1274, 1592, 1594), e.g. "direct PR lane share <X>", overstating the reward a contributor should expect.
Failure mode (concrete example)
Repo with emissionShare = 0.02, issueDiscoveryShare = 1 (a pure issue-discovery repo):
- Current:
rewardUpside.issueDiscoveryShare = 0.02 * 1 = 0.02.
- Correct:
0.02 * 0.9 * 1 = 0.018, matching preview.laneMath.issueDiscoverySlice for the same repo.
The decision pack tells the miner the issue-discovery lane upside is 0.02 and ranks the repo accordingly, while the scoring preview / reward-risk surface says 0.018. The contributor sees an ~11% inflated upside and a correspondingly inflated priority.
Steps to reproduce
- Call
buildRepoDecision (or build a full contributor decision pack) for a registered repo with a non-zero emissionShare.
- Compare
decision.rewardUpside.directPrShare / issueDiscoveryShare against the same repo's buildScorePreview(...).laneMath.directPrSlice / issueDiscoverySlice.
- Observe the decision-pack values are larger by a factor of
1 / OSS_EMISSION_SHARE (≈1.111), and that directPrShare can equal the full raw emissionShare (exceeding laneMath.repoSlice).
Expected behavior
Decision-pack lane shares match the canonical lane math: directPrShare = emissionShare * OSS_EMISSION_SHARE * (1 - issueDiscoveryShare) and issueDiscoveryShare = emissionShare * OSS_EMISSION_SHARE * issueDiscoveryShare, consistent with preview.laneMath and reward-risk.rewardUpside. The raw emissionShare field stays raw (it mirrors laneMath.repoEmissionShare).
Actual behavior
The lane shares are computed off the raw emissionShare with no OSS_EMISSION_SHARE factor, so they are inflated by ~11.1% and a single lane share can exceed the repo's entire mining pool, inflating priorityFor's upside and the repo's priorityScore.
Suggested fix
- Thread the
OSS_EMISSION_SHARE constant into buildRepoDecision from the scoring snapshot that the decision-pack builder already loads (scoringSnapshot is in scope at the buildContributorDecisionPack call site, decision-pack.ts:462), defaulting to 0.9 for direct callers — mirroring constant(constants, "OSS_EMISSION_SHARE", 0.9) in preview.ts.
- Multiply the two lane shares by that factor:
const ossEmissionShare = args.ossEmissionShare ?? 0.9;
const baseEmission = config?.emissionShare ?? 0;
const rewardUpside = {
emissionShare: round(baseEmission), // raw, unchanged
directPrShare: round(baseEmission * ossEmissionShare * (1 - (config?.issueDiscoveryShare ?? 0))),
issueDiscoveryShare: round(baseEmission * ossEmissionShare * (config?.issueDiscoveryShare ?? 0)),
maintainerCut: round(config?.maintainerCut ?? 0),
};
- Add fail-on-revert coverage: a
buildRepoDecision whose rewardUpside.issueDiscoveryShare / directPrShare equals the OSS-adjusted value (and matches buildScorePreview(...).laneMath), not the raw split.
Summary
buildRepoDecision(src/services/decision-pack.ts) computes a repo'srewardUpsidelane shares straight from the raw registryemissionShare, without theOSS_EMISSION_SHARE(0.9) factor that every other path applies:The canonical lane math in
buildScorePreview(src/scoring/preview.ts:270-322) applies the factor and keeps the raw emission share distinct from the OSS-adjusted slices:So
directPrShare/issueDiscoverySharein the decision pack are the analogs of preview'sdirectPrSlice/issueDiscoverySlice, but they are computed off the raw emission share and are therefore inflated by1 / 0.9 ≈ 11.1%.Why this is wrong
OSS_EMISSION_SHAREis the fraction of a repo's emission that is actually available to OSS mining (the rest is allocated elsewhere).directPrShare/issueDiscoveryShareare meant to represent the tokens a miner can earn in each lane — i.e. a split of the mining pool, exactly likedirectPrSlice/issueDiscoverySliceinpreview.ts.A direct proof the value is impossible: with
issueDiscoveryShare = 0, the current code yieldsdirectPrShare = emissionShare— equal to the repo's entire raw emission. But the whole OSS mining pool for the repo is onlyemissionShare * 0.9(preview.repoSlice). A single lane's share thus exceeds the total mining pool it is drawn from, which cannot be correct.The two paths also disagree with each other:
reward-risk.ts(buildRepoRewardRisk, line 283-288) builds itsrewardUpsidefromcurrentPreview.laneMath.repoSlice / directPrSlice / issueDiscoverySlice— the correctly OSS-adjusted values.decision-pack.tsre-derives the lane shares itself from the rawemissionShareand drops the factor.So for the same repo, the reward-risk surface and the decision-pack surface report different lane upside, and the decision-pack one is too high.
Downstream impact
rewardUpsideis not cosmetic — it drives the repo ranking.priorityFor(decision-pack.ts:1094-1109) uses:Because
directPrShare/issueDiscoveryShareare inflated,upsideis inflated, sopriorityScoreis too high.priorityScoreis what sortspursueRepos,cleanupFirst,topActions, etc. (decision-pack.ts:589-590, 642-645). The bias is not uniform: repos whose registry split makes theMath.maxresolve to a lane share (rather than theemissionShare * 0.35floor term, which uses the raw value and is unchanged) get a bigger boost, so the relative ranking of repos is distorted, not just shifted by a constant.The inflated
directPrShare/issueDiscoveryShareare also rendered verbatim in the miner-facing reason strings (decision-pack.ts:1274, 1592, 1594), e.g."direct PR lane share <X>", overstating the reward a contributor should expect.Failure mode (concrete example)
Repo with
emissionShare = 0.02,issueDiscoveryShare = 1(a pure issue-discovery repo):rewardUpside.issueDiscoveryShare = 0.02 * 1 = 0.02.0.02 * 0.9 * 1 = 0.018, matchingpreview.laneMath.issueDiscoverySlicefor the same repo.The decision pack tells the miner the issue-discovery lane upside is
0.02and ranks the repo accordingly, while the scoring preview / reward-risk surface says0.018. The contributor sees an ~11% inflated upside and a correspondingly inflated priority.Steps to reproduce
buildRepoDecision(or build a full contributor decision pack) for a registered repo with a non-zeroemissionShare.decision.rewardUpside.directPrShare/issueDiscoveryShareagainst the same repo'sbuildScorePreview(...).laneMath.directPrSlice/issueDiscoverySlice.1 / OSS_EMISSION_SHARE(≈1.111), and thatdirectPrSharecan equal the full rawemissionShare(exceedinglaneMath.repoSlice).Expected behavior
Decision-pack lane shares match the canonical lane math:
directPrShare = emissionShare * OSS_EMISSION_SHARE * (1 - issueDiscoveryShare)andissueDiscoveryShare = emissionShare * OSS_EMISSION_SHARE * issueDiscoveryShare, consistent withpreview.laneMathandreward-risk.rewardUpside. The rawemissionSharefield stays raw (it mirrorslaneMath.repoEmissionShare).Actual behavior
The lane shares are computed off the raw
emissionSharewith noOSS_EMISSION_SHAREfactor, so they are inflated by ~11.1% and a single lane share can exceed the repo's entire mining pool, inflatingpriorityFor'supsideand the repo'spriorityScore.Suggested fix
OSS_EMISSION_SHAREconstant intobuildRepoDecisionfrom the scoring snapshot that the decision-pack builder already loads (scoringSnapshotis in scope at thebuildContributorDecisionPackcall site,decision-pack.ts:462), defaulting to0.9for direct callers — mirroringconstant(constants, "OSS_EMISSION_SHARE", 0.9)inpreview.ts.buildRepoDecisionwhoserewardUpside.issueDiscoveryShare/directPrShareequals the OSS-adjusted value (and matchesbuildScorePreview(...).laneMath), not the raw split.