Skip to content
Merged
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
24 changes: 20 additions & 4 deletions src/services/decision-pack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
} from "../db/repositories";
import { contributorRepoStatsFromGittensor, fetchGittensorContributorSnapshot } from "../gittensor/api";
import { fetchPublicContributorProfile } from "../github/public";
import { getOrCreateScoringModelSnapshot } from "../scoring/model";
import { DEFAULT_SCORING_CONSTANTS, getOrCreateScoringModelSnapshot } from "../scoring/model";
import {
buildContributorFit,
buildContributorOutcomeHistory,
Expand Down Expand Up @@ -70,6 +70,12 @@ import { nowIso } from "../utils/json";

export const CONTRIBUTOR_DECISION_PACK_SIGNAL = "contributor-decision-pack";
export const DECISION_PACK_MAX_AGE_MS = 6 * 60 * 60 * 1000;
const DEFAULT_OSS_EMISSION_SHARE = DEFAULT_SCORING_CONSTANTS.OSS_EMISSION_SHARE ?? 0.9;

function resolveOssEmissionShare(constants: Record<string, number> | undefined): number {
const value = constants?.OSS_EMISSION_SHARE;
return typeof value === "number" && Number.isFinite(value) ? value : DEFAULT_OSS_EMISSION_SHARE;
}
export const DECISION_PACK_REBUILD_DEBOUNCE_MS = 15 * 1000;
const pendingDecisionPackRebuilds = new Map<string, Promise<boolean>>();

Expand Down Expand Up @@ -469,6 +475,7 @@ export async function buildAndPersistContributorDecisionPack(env: Env, login: st
totals,
opportunities: fit.opportunities,
scoringModelSnapshotId: scoringSnapshot.id,
ossEmissionShare: resolveOssEmissionShare(scoringSnapshot.constants),
contributorPullRequests,
contributorIssues,
repoStats,
Expand Down Expand Up @@ -546,6 +553,7 @@ function buildContributorDecisionPack(args: {
focusManifests?: Map<string, FocusManifest> | undefined;
repoOutcomePatternsByRepo?: Map<string, RepoOutcomePatterns> | undefined;
recommendationOutcomeFeedback?: AgentRecommendationOutcomeSummary | undefined;
ossEmissionShare?: number | undefined;
}): ContributorDecisionPack {
const recommendationOutcomeFeedback = args.recommendationOutcomeFeedback ?? emptyRecommendationOutcomeFeedback(args.login);
const registeredRepositories = args.repositories.filter((repo) => repo.isRegistered);
Expand Down Expand Up @@ -584,6 +592,7 @@ function buildContributorDecisionPack(args: {
focusManifest: args.focusManifests?.get(key),
repoOutcomePatterns: args.repoOutcomePatternsByRepo?.get(key),
recommendationOutcomeFeedback: recommendationFeedbackByRepo.get(key),
ossEmissionShare: args.ossEmissionShare,
});
})
.sort((left, right) => right.priorityScore - left.priorityScore || left.repoFullName.localeCompare(right.repoFullName));
Expand Down Expand Up @@ -665,9 +674,12 @@ function buildRepoDecision(args: {
focusManifest?: FocusManifest | undefined;
repoOutcomePatterns?: RepoOutcomePatterns | undefined;
recommendationOutcomeFeedback?: AgentRecommendationOutcomeRepoSummary | undefined;
ossEmissionShare?: number | undefined;
}): RepoDecision {
const lane = buildLaneAdvice(args.repo, args.repo.fullName);
const config = args.repo.registryConfig;
const ossEmissionShare =
typeof args.ossEmissionShare === "number" && Number.isFinite(args.ossEmissionShare) ? args.ossEmissionShare : DEFAULT_OSS_EMISSION_SHARE;
const openPullRequests = args.totals?.openPullRequestsTotal ?? args.syncState?.openPullRequestsCount ?? 0;
const openIssues = args.totals?.openIssuesTotal ?? args.syncState?.openIssuesCount ?? 0;
const queue = {
Expand All @@ -676,10 +688,14 @@ function buildRepoDecision(args: {
mergedPullRequests: args.totals?.mergedPullRequestsTotal ?? args.syncState?.recentMergedPullRequestsCount ?? 0,
closedUnmergedPullRequests: args.totals?.closedUnmergedPullRequestsTotal ?? 0,
};
const baseEmissionShare = config?.emissionShare ?? 0;
// Lane shares are a split of the OSS *mining* pool (emissionShare * OSS_EMISSION_SHARE), matching
// preview.ts laneMath (directPrSlice/issueDiscoverySlice) and reward-risk.ts. The raw emissionShare
// field stays raw (it mirrors laneMath.repoEmissionShare).
const rewardUpside = {
emissionShare: round(config?.emissionShare ?? 0),
directPrShare: round((config?.emissionShare ?? 0) * (1 - (config?.issueDiscoveryShare ?? 0))),
issueDiscoveryShare: round((config?.emissionShare ?? 0) * (config?.issueDiscoveryShare ?? 0)),
emissionShare: round(baseEmissionShare),
directPrShare: round(baseEmissionShare * ossEmissionShare * (1 - (config?.issueDiscoveryShare ?? 0))),
issueDiscoveryShare: round(baseEmissionShare * ossEmissionShare * (config?.issueDiscoveryShare ?? 0)),
maintainerCut: round(config?.maintainerCut ?? 0),
};
const blockers = scoreBlockersFor(args.repo.fullName, lane.lane, args.roleContext, args.outcome);
Expand Down
30 changes: 28 additions & 2 deletions test/unit/decision-pack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe("decision-pack service", () => {
outcome: undefined,
syncState: { openPullRequestsCount: 1, openIssuesCount: 2, recentMergedPullRequestsCount: 3 } as any,
}),
).toMatchObject({ recommendation: "watch", queue: { openPullRequests: 1, openIssues: 2, mergedPullRequests: 3 }, rewardUpside: { issueDiscoveryShare: 0.02 } });
).toMatchObject({ recommendation: "watch", queue: { openPullRequests: 1, openIssues: 2, mergedPullRequests: 3 }, rewardUpside: { issueDiscoveryShare: 0.018 } });
expect(
__decisionPackInternals.buildRepoDecision({
repo: repo("owner/inactive", 0, 0),
Expand All @@ -111,6 +111,31 @@ describe("decision-pack service", () => {
expect(__decisionPackInternals.round(1.23456)).toBe(1.2346);
});

it("applies OSS_EMISSION_SHARE to rewardUpside lane shares (not the raw emission share)", () => {
const outsideRole = { maintainerLane: false } as any;
// Split repo: emissionShare 0.04, issueDiscoveryShare 0.25. Lane shares are a split of the OSS
// mining pool (emissionShare * 0.9), matching preview.ts laneMath / reward-risk.ts -- NOT the raw split.
const split = __decisionPackInternals.buildRepoDecision({
repo: repo("owner/split", 0.04, 0.25),
roleContext: outsideRole,
outcome: undefined,
}).rewardUpside;
expect(split.emissionShare).toBe(0.04); // raw value preserved (mirrors laneMath.repoEmissionShare)
expect(split.directPrShare).toBeCloseTo(0.04 * 0.9 * 0.75, 10); // 0.027
expect(split.issueDiscoveryShare).toBeCloseTo(0.04 * 0.9 * 0.25, 10); // 0.009
// A single lane share can never exceed the repo's whole OSS mining pool (emissionShare * 0.9).
expect(split.directPrShare).toBeLessThanOrEqual(0.04 * 0.9 + 1e-9);

// An explicit snapshot-provided OSS_EMISSION_SHARE override is honored.
const overridden = __decisionPackInternals.buildRepoDecision({
repo: repo("owner/override", 0.04, 0),
roleContext: outsideRole,
outcome: undefined,
ossEmissionShare: 0.8,
}).rewardUpside;
expect(overridden.directPrShare).toBeCloseTo(0.04 * 0.8, 10); // 0.032
});

it("feeds repo outcome patterns into repo decisions without inflating maintainer-lane evidence", () => {
const outsideRole = { maintainerLane: false } as any;
const maintainerRole = { maintainerLane: true } as any;
Expand Down Expand Up @@ -1048,7 +1073,8 @@ describe("decision-pack service", () => {
summary: "0 open issues evaluated.",
},
});
expect(emptyQualityDecision.priorityScore).toBe(40);
// 38, not 40: upside = issueDiscoveryShare (0.02 * OSS_EMISSION_SHARE 0.9 = 0.018) * 1000 = 18.
expect(emptyQualityDecision.priorityScore).toBe(38);
});

it("issues avoid_for_now reasoning with sanitized public copy", () => {
Expand Down